feat: implement consul ACL management with provider aliases

This commit message captures the major architectural change of implementing Consul ACL management
with proper provider aliasing, along with the supporting configuration files and policy definitions
for various terraform services.

- add consul_acl_management module to manage consul acl policies and roles
- add consul backend roles and policies for terraform services (incus, k8s, nomad, repoflow, vault)
- add consul provider configuration to root.hcl
- add policies to generate credentials for each role
- simplify consul_secret_backend_role module to reference acl-managed roles
- switch to opentofu for provider foreach support
- update terragrunt configuration to support consul backend aliases
- update pre-commit hooks to use opentofu instead of terraform
- configure tflint exceptions for consul acl management module
This commit is contained in:
2026-02-08 15:55:30 +11:00
parent f8f1185b42
commit 5536869a38
30 changed files with 318 additions and 24 deletions
+25 -1
View File
@@ -237,6 +237,29 @@ module "consul_secret_backend" {
max_lease_ttl_seconds = each.value.max_lease_ttl_seconds
}
# Create data sources for consul backend tokens
data "vault_kv_secret_v2" "consul_backend_configs" {
for_each = {
for k, v in var.consul_secret_backend : k => v
if !v.bootstrap
}
mount = "kv"
name = "service/vault/${var.country}/${var.region}/secret_backend/${each.key}"
}
# Create Consul ACL management module
module "consul_acl_management" {
source = "./modules/consul_acl_management"
country = var.country
region = var.region
consul_backends = var.consul_secret_backend
consul_roles = var.consul_secret_backend_role
consul_backend_aliases = var.consul_backend_aliases
}
# Create consul secret backend roles (Vault resources only)
module "consul_secret_backend_role" {
source = "./modules/consul_secret_backend_role"
@@ -249,7 +272,7 @@ module "consul_secret_backend_role" {
max_ttl = each.value.max_ttl
local = each.value.local
depends_on = [module.consul_secret_backend]
depends_on = [module.consul_secret_backend, module.consul_acl_management]
}
module "kubernetes_secret_backend" {
@@ -314,3 +337,4 @@ module "pki_mount_only" {
enable_delta = each.value.enable_delta
delta_rebuild_interval = each.value.delta_rebuild_interval
}
@@ -0,0 +1,7 @@
rule "terraform_required_providers" {
enabled = false
}
rule "terraform_required_version" {
enabled = false
}
@@ -0,0 +1,58 @@
# Get consul backend tokens from Vault
data "vault_kv_secret_v2" "consul_backend_configs" {
for_each = var.consul_backends
mount = "kv"
name = "service/vault/${var.country}/${var.region}/secret_backend/${each.key}"
}
# Create consul provider instances for each consul backend
provider "consul" {
alias = "by_backend"
for_each = var.consul_backend_aliases
address = var.consul_backends[each.key].address
scheme = var.consul_backends[each.key].scheme
ca_file = "/etc/pki/tls/certs/ca-bundle.crt"
token = data.vault_kv_secret_v2.consul_backend_configs[each.key].data["token"]
}
# Create Consul ACL policies
resource "consul_acl_policy" "policies" {
for_each = var.consul_roles
provider = consul.by_backend[each.value.backend]
name = each.value.name
description = each.value.description != null ? each.value.description : "Auto-generated policy for Vault role ${each.value.name}"
rules = file("${path.module}/../../../../../../../../resources/secret_backend/${each.value.backend}/${each.value.name}.hcl")
datacenters = each.value.datacenters
}
# Create Consul ACL roles
resource "consul_acl_role" "roles" {
for_each = var.consul_roles
provider = consul.by_backend[each.value.backend]
name = each.value.name
description = each.value.description != null ? each.value.description : "Auto-generated role for Vault role ${each.value.name}"
policies = [consul_acl_policy.policies[each.key].name]
dynamic "service_identities" {
for_each = each.value.service_identities != null ? each.value.service_identities : []
content {
service_name = service_identities.value.service_name
datacenters = service_identities.value.datacenters
}
}
dynamic "node_identities" {
for_each = each.value.node_identities != null ? each.value.node_identities : []
content {
node_name = node_identities.value.node_name
datacenter = node_identities.value.datacenter
}
}
}
@@ -0,0 +1,9 @@
output "consul_acl_policies" {
description = "Map of created Consul ACL policies"
value = consul_acl_policy.policies
}
output "consul_acl_roles" {
description = "Map of created Consul ACL roles"
value = consul_acl_role.roles
}
@@ -0,0 +1,46 @@
variable "consul_backends" {
description = "Map of consul secret backends"
type = map(object({
address = string
scheme = string
bootstrap = bool
bootstrap_token = optional(string)
ca_cert = optional(string)
client_cert = optional(string)
client_key = optional(string)
}))
}
variable "consul_roles" {
description = "Map of consul secret backend roles"
type = map(object({
name = string
backend = string
description = optional(string)
datacenters = optional(list(string))
service_identities = optional(list(object({
service_name = string
datacenters = optional(list(string))
})))
node_identities = optional(list(object({
node_name = string
datacenter = string
})))
}))
}
variable "consul_backend_aliases" {
description = "Map of consul backend names to sanitized provider aliases"
type = map(string)
default = {}
}
variable "country" {
description = "Country identifier"
type = string
}
variable "region" {
description = "Region identifier"
type = string
}
@@ -18,4 +18,4 @@ resource "vault_consul_secret_backend" "consul" {
client_key = var.client_key
default_lease_ttl_seconds = var.default_lease_ttl_seconds
max_lease_ttl_seconds = var.max_lease_ttl_seconds
}
}
@@ -1,8 +1,9 @@
# Create Vault Consul secret backend role
resource "vault_consul_secret_backend_role" "role" {
backend = var.backend
name = var.name
consul_roles = var.consul_roles
consul_roles = [var.name] # Use the role name created by consul_acl_management module
ttl = var.ttl
max_ttl = var.max_ttl
local = var.local
}
}
@@ -32,4 +32,5 @@ variable "local" {
description = "Whether tokens should be local to the datacenter"
type = bool
default = false
}
}
+18
View File
@@ -226,6 +226,7 @@ variable "consul_secret_backend" {
description = optional(string)
address = string
bootstrap = optional(bool, false)
bootstrap_token = optional(string)
scheme = optional(string, "https")
ca_cert = optional(string)
client_cert = optional(string)
@@ -245,10 +246,26 @@ variable "consul_secret_backend_role" {
ttl = optional(number)
max_ttl = optional(number)
local = optional(bool, false)
datacenters = optional(list(string))
description = optional(string)
service_identities = optional(list(object({
service_name = string
datacenters = optional(list(string))
})))
node_identities = optional(list(object({
node_name = string
datacenter = string
})))
}))
default = {}
}
variable "consul_backend_aliases" {
description = "Map of consul backend names to sanitized provider aliases"
type = map(string)
default = {}
}
variable "kubernetes_secret_backend" {
description = "Map of Kubernetes secret engines to create"
type = map(object({
@@ -287,3 +304,4 @@ variable "policy_rules_map" {
})))
default = {}
}