cloudflare management for cms
This commit is contained in:
@@ -29,8 +29,8 @@ concurrency:
|
|||||||
path: gitea_jwt
|
path: gitea_jwt
|
||||||
secrets: |
|
secrets: |
|
||||||
kvv1/google/credentials credentials | GOOGLE_CREDENTIALS ;
|
kvv1/google/credentials credentials | GOOGLE_CREDENTIALS ;
|
||||||
kvv1/admin/gitea token | GITEA_TOKEN
|
kvv1/admin/gitea token | GITEA_TOKEN ;
|
||||||
|
kvv1/admin/cloudflare iam_token | CLOUDFLARE_API_TOKEN ;
|
||||||
jobs:
|
jobs:
|
||||||
gitea_vault_auth:
|
gitea_vault_auth:
|
||||||
name: Auth with gitea for vault
|
name: Auth with gitea for vault
|
||||||
|
|||||||
76
iac/cloudflare_ovh_cms.tf
Normal file
76
iac/cloudflare_ovh_cms.tf
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
data "cloudflare_account" "arcodange" {
|
||||||
|
filter = {
|
||||||
|
name = "arcodange@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
cloudflare_account_id = data.cloudflare_account.arcodange.account_id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "cloudflare_r2_bucket" "arcodange_tf" {
|
||||||
|
account_id = local.cloudflare_account_id
|
||||||
|
name = "arcodange-tf"
|
||||||
|
jurisdiction = "eu"
|
||||||
|
}
|
||||||
|
|
||||||
|
module "cf_r2_arcodange_tf_token" {
|
||||||
|
source = "./modules/cloudflare_token"
|
||||||
|
account_id = local.cloudflare_account_id
|
||||||
|
bucket = cloudflare_r2_bucket.arcodange_tf
|
||||||
|
token_name = "r2_arcodange_tf_token"
|
||||||
|
permissions = {
|
||||||
|
bucket = [
|
||||||
|
"account:Workers R2 Storage Read",
|
||||||
|
"bucket:Workers R2 Storage Bucket Item Write",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "vault_kv_secret" "cf_r2_arcodange_tf" {
|
||||||
|
path = "kvv1/cloudflare/r2/arcodange-tf"
|
||||||
|
data_json = jsonencode({
|
||||||
|
S3_SECRET_ACCESS_KEY = module.cf_r2_arcodange_tf_token.r2_credentials.secret_access_key
|
||||||
|
S3_ACCESS_KEY = module.cf_r2_arcodange_tf_token.r2_credentials.access_key_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
data "vault_policy_document" "cf_r2_arcodange_tf" {
|
||||||
|
rule {
|
||||||
|
path = "kvv1/cloudflare/r2/arcodange-tf"
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "vault_policy" "cf_r2_arcodange_tf" {
|
||||||
|
name = "factory__cf_r2_arcodange_tf"
|
||||||
|
policy = data.vault_policy_document.cf_r2_arcodange_tf.hcl
|
||||||
|
}
|
||||||
|
|
||||||
|
data "gitea_repo" "cms" {
|
||||||
|
name = "cms"
|
||||||
|
username = "arcodange-org"
|
||||||
|
}
|
||||||
|
module "cf_arcodange_cms_token" {
|
||||||
|
source = "./modules/cloudflare_token"
|
||||||
|
account_id = local.cloudflare_account_id
|
||||||
|
bucket = cloudflare_r2_bucket.arcodange_tf
|
||||||
|
token_name = "cf_arcodange_cms_token"
|
||||||
|
permissions = {
|
||||||
|
account = [
|
||||||
|
"account:Pages Write",
|
||||||
|
"account:Account DNS Settings Write",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "gitea_repository_actions_secret" "cf_arcodange_cms_token" {
|
||||||
|
repository = data.gitea_repo.cms.name
|
||||||
|
repository_owner = data.gitea_repo.cms.username
|
||||||
|
secret_name = "CLOUDFLARE_API_TOKEN"
|
||||||
|
secret_value = module.cf_arcodange_cms_token.token
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_kv_secret" "cf_arcodange_cms_token" {
|
||||||
|
path = "kvv1/cloudflare/cms/cf_arcodange_cms_token"
|
||||||
|
data_json = jsonencode({
|
||||||
|
token = module.cf_arcodange_cms_token.token
|
||||||
|
})
|
||||||
|
}
|
||||||
79
iac/modules/cloudflare_token/main.tf
Normal file
79
iac/modules/cloudflare_token/main.tf
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# 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
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 = local.account_resource
|
||||||
|
} : null,
|
||||||
|
|
||||||
|
length(local.selected_bucket_permissions) > 0 ? {
|
||||||
|
effect = "allow"
|
||||||
|
permission_groups = [for id in local.selected_bucket_permissions : { id = id }]
|
||||||
|
resources = local.bucket_resource
|
||||||
|
} : 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]
|
||||||
|
replace_triggered_by = [null_resource.cloudflare_account_token_replace]
|
||||||
|
precondition {
|
||||||
|
condition = length(local.missing_permissions) == 0
|
||||||
|
error_message = local.error_message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "null_resource" "cloudflare_account_token_replace" {
|
||||||
|
triggers = {
|
||||||
|
"policies" = sha256(join("", local.selected_account_permissions, local.selected_bucket_permissions))
|
||||||
|
}
|
||||||
|
}
|
||||||
35
iac/modules/cloudflare_token/outputs.tf
Normal file
35
iac/modules/cloudflare_token/outputs.tf
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
output "token" {
|
||||||
|
description = "Valeur du token Cloudflare"
|
||||||
|
value = cloudflare_account_token.token.value
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output "token_id" {
|
||||||
|
description = "ID du token Cloudflare (sert de Access Key ID pour R2 si bucket défini)"
|
||||||
|
value = cloudflare_account_token.token.id
|
||||||
|
}
|
||||||
|
|
||||||
|
output "token_sha256" {
|
||||||
|
description = "SHA-256 du token Cloudflare (sert de Secret Access Key pour R2 si bucket défini)"
|
||||||
|
value = sha256(cloudflare_account_token.token.value)
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output "r2_credentials" {
|
||||||
|
description = "Credentials R2 si bucket configuré (AccessKeyId, SecretAccessKey)"
|
||||||
|
value = var.bucket != null ? {
|
||||||
|
access_key_id = cloudflare_account_token.token.id
|
||||||
|
secret_access_key = sha256(cloudflare_account_token.token.value)
|
||||||
|
} : null
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output "permissions" {
|
||||||
|
description = "Liste des permissions introuvables (si existantes)"
|
||||||
|
value = compact(concat(local.selected_account_permissions, local.selected_bucket_permissions))
|
||||||
|
}
|
||||||
|
|
||||||
|
output "resources" {
|
||||||
|
description = "Map des resources assignées au token"
|
||||||
|
value = keys(merge(local.account_resource, local.bucket_resource))
|
||||||
|
}
|
||||||
37
iac/modules/cloudflare_token/variables.tf
Normal file
37
iac/modules/cloudflare_token/variables.tf
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
variable "account_id" {
|
||||||
|
description = "Cloudflare account ID"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "token_name" {
|
||||||
|
description = "Nom du token Cloudflare à créer"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "permissions" {
|
||||||
|
description = <<-EOT
|
||||||
|
Liste des permissions Cloudflare (ex: [\"Pages Deploy\", \"Zone DNS Edit\"])
|
||||||
|
you can check required permissions per service
|
||||||
|
https://developers.cloudflare.com/api/node/
|
||||||
|
EOT
|
||||||
|
type = object({
|
||||||
|
account = optional(list(string))
|
||||||
|
bucket = optional(list(string))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "bucket" {
|
||||||
|
description = <<-EOT
|
||||||
|
Objet optionnel représentant un bucket R2.
|
||||||
|
Exemple :
|
||||||
|
{
|
||||||
|
name = "mon-bucket"
|
||||||
|
jurisdiction = "eu"
|
||||||
|
}
|
||||||
|
EOT
|
||||||
|
type = object({
|
||||||
|
name = string
|
||||||
|
jurisdiction = string
|
||||||
|
})
|
||||||
|
default = null
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
gitea = {
|
gitea = {
|
||||||
source = "go-gitea/gitea"
|
source = "go-gitea/gitea"
|
||||||
version = "0.5.1"
|
version = "0.6.0"
|
||||||
}
|
}
|
||||||
vault = {
|
vault = {
|
||||||
source = "vault"
|
source = "vault"
|
||||||
@@ -12,6 +12,10 @@ terraform {
|
|||||||
source = "google"
|
source = "google"
|
||||||
version = "7.0.1"
|
version = "7.0.1"
|
||||||
}
|
}
|
||||||
|
cloudflare = {
|
||||||
|
source = "cloudflare/cloudflare"
|
||||||
|
version = "~> 5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,3 +36,5 @@ provider "google" {
|
|||||||
project = "arcodange"
|
project = "arcodange"
|
||||||
region = "US-EAST1"
|
region = "US-EAST1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "cloudflare" {} # CLOUDFLARE_API_TOKEN environment variable required
|
||||||
Reference in New Issue
Block a user