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:
parent
f8f1185b42
commit
5536869a38
@ -9,8 +9,8 @@ repos:
|
||||
- repo: https://github.com/gruntwork-io/pre-commit
|
||||
rev: v0.1.30
|
||||
hooks:
|
||||
- id: terraform-fmt
|
||||
- id: terraform-validate
|
||||
- id: tofu-fmt
|
||||
- id: tofu-validate
|
||||
- id: tflint
|
||||
- id: terragrunt-hcl-fmt
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
|
||||
4
Makefile
4
Makefile
@ -23,7 +23,7 @@ apply: init
|
||||
@terragrunt run --all --parallelism 2 --non-interactive apply
|
||||
|
||||
format:
|
||||
@echo "Formatting Terraform files..."
|
||||
@terraform fmt -recursive .
|
||||
@echo "Formatting OpenTofu files..."
|
||||
@tofu fmt -recursive .
|
||||
@echo "Formatting Terragrunt files..."
|
||||
@terragrunt hcl fmt
|
||||
|
||||
@ -169,7 +169,7 @@ locals {
|
||||
}
|
||||
consul_secret_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
trimsuffix(replace(file_path, "consul_secret_backend/", ""), ".yaml") => content
|
||||
if startswith(file_path, "consul_secret_backend/")
|
||||
}
|
||||
consul_secret_backend_role = {
|
||||
@ -186,4 +186,4 @@ locals {
|
||||
if startswith(file_path, "pki_mount_only/")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
config/consul_secret_backend/consul_root/au/syd1.yaml
Normal file
7
config/consul_secret_backend/consul_root/au/syd1.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
description: "consul secret engine for au-syd1 cluster"
|
||||
default_lease_ttl_seconds: 600
|
||||
max_lease_ttl_seconds: 86400
|
||||
address: "consul.service.au-syd1.consul"
|
||||
scheme: https
|
||||
bootstrap: false
|
||||
datacenter: au-syd1
|
||||
@ -0,0 +1,5 @@
|
||||
consul_roles:
|
||||
- terraform-incus
|
||||
ttl: 300
|
||||
max_ttl: 600
|
||||
datacenters: []
|
||||
@ -0,0 +1,5 @@
|
||||
consul_roles:
|
||||
- terraform-k8s
|
||||
ttl: 120
|
||||
max_ttl: 300
|
||||
datacenters: []
|
||||
@ -0,0 +1,5 @@
|
||||
consul_roles:
|
||||
- terraform-nomad
|
||||
ttl: 120
|
||||
max_ttl: 300
|
||||
datacenters: []
|
||||
@ -0,0 +1,5 @@
|
||||
consul_roles:
|
||||
- terraform-repoflow
|
||||
ttl: 120
|
||||
max_ttl: 300
|
||||
datacenters: []
|
||||
@ -0,0 +1,5 @@
|
||||
consul_roles:
|
||||
- terraform-vault
|
||||
ttl: 120
|
||||
max_ttl: 300
|
||||
datacenters: []
|
||||
@ -13,6 +13,11 @@ include "policies" {
|
||||
expose = true
|
||||
}
|
||||
|
||||
include "resources" {
|
||||
path = "${get_repo_root()}/resources/resources.hcl"
|
||||
expose = true
|
||||
}
|
||||
|
||||
locals {
|
||||
# Extract country and region from path
|
||||
path_parts = split("/", dirname(get_terragrunt_dir()))
|
||||
@ -24,6 +29,16 @@ locals {
|
||||
|
||||
# Include policies from policies.hcl
|
||||
policies = include.policies.locals
|
||||
|
||||
# Include resources from resources.hcl
|
||||
resources = include.resources.locals
|
||||
|
||||
# Create sanitized backend name mapping for Consul providers
|
||||
# Provider aliases can't contain slashes, so replace them with underscores
|
||||
consul_backend_aliases = {
|
||||
for backend_name, _ in local.config.consul_secret_backend :
|
||||
backend_name => replace(backend_name, "/", "_")
|
||||
}
|
||||
}
|
||||
|
||||
terraform {
|
||||
@ -57,4 +72,7 @@ inputs = {
|
||||
# Pass policy maps to vault_cluster module
|
||||
policy_auth_map = local.policies.policy_auth_map
|
||||
policy_rules_map = local.policies.policy_rules_map
|
||||
|
||||
# Pass sanitized consul backend aliases for provider configuration
|
||||
consul_backend_aliases = local.consul_backend_aliases
|
||||
}
|
||||
|
||||
@ -3,27 +3,14 @@ generate "backend" {
|
||||
path = "backend.tf"
|
||||
if_exists = "overwrite"
|
||||
contents = <<EOF
|
||||
#-------------------------------------------
|
||||
# locals
|
||||
#-------------------------------------------
|
||||
locals {
|
||||
vault_addr = "https://vault.service.consul:8200"
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Configure this provider through the environment variables:
|
||||
# - VAULT_ADDR
|
||||
# - VAULT_TOKEN
|
||||
#-----------------------------------------------------------------------------
|
||||
provider "vault" {
|
||||
address = local.vault_addr
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Use remote state file and encrypt it since your state files may contains
|
||||
# sensitive data.
|
||||
# export CONSUL_HTTP_TOKEN=<your-token>
|
||||
#------------------------------------------------------------------------------
|
||||
terraform {
|
||||
backend "consul" {
|
||||
address = "https://consul.service.consul"
|
||||
@ -38,6 +25,10 @@ terraform {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
consul = {
|
||||
source = "hashicorp/consul"
|
||||
version = "2.23.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
@ -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
|
||||
}
|
||||
58
modules/vault_cluster/modules/consul_acl_management/main.tf
Normal file
58
modules/vault_cluster/modules/consul_acl_management/main.tf
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 = {}
|
||||
}
|
||||
|
||||
|
||||
10
policies/consul_root/au/syd1/creds/terraform-incus.yaml
Normal file
10
policies/consul_root/au/syd1/creds/terraform-incus.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
# generate credentials for the terraform-incus role in consul
|
||||
---
|
||||
rules:
|
||||
- path: "consul_root/au/syd1/creds/terraform-incus"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- terraform_incus
|
||||
10
policies/consul_root/au/syd1/creds/terraform-k8s.yaml
Normal file
10
policies/consul_root/au/syd1/creds/terraform-k8s.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
# generate credentials for the terraform-k8s role in consul
|
||||
---
|
||||
rules:
|
||||
- path: "consul_root/au/syd1/creds/terraform-k8s"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- terraform_k8s
|
||||
10
policies/consul_root/au/syd1/creds/terraform-nomad.yaml
Normal file
10
policies/consul_root/au/syd1/creds/terraform-nomad.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
# generate credentials for the terraform-nomad role in consul
|
||||
---
|
||||
rules:
|
||||
- path: "consul_root/au/syd1/creds/terraform-nomad"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- terraform_nomad
|
||||
10
policies/consul_root/au/syd1/creds/terraform-repoflow.yaml
Normal file
10
policies/consul_root/au/syd1/creds/terraform-repoflow.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
# generate credentials for the terraform-repoflow role in consul
|
||||
---
|
||||
rules:
|
||||
- path: "consul_root/au/syd1/creds/terraform-repoflow"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- terraform_repoflow
|
||||
10
policies/consul_root/au/syd1/creds/terraform-vault.yaml
Normal file
10
policies/consul_root/au/syd1/creds/terraform-vault.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
# generate credentials for the terraform-vault role in consul
|
||||
---
|
||||
rules:
|
||||
- path: "consul_root/au/syd1/creds/terraform-vault"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- tf_vault
|
||||
@ -0,0 +1,7 @@
|
||||
key_prefix "infra/terraform/incus/" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
session_prefix "" {
|
||||
policy = "write"
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
key_prefix "infra/terraform/k8s/" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
session_prefix "" {
|
||||
policy = "write"
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
key_prefix "infra/terraform/nomad/" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
session_prefix "" {
|
||||
policy = "write"
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
key_prefix "infra/terraform/repoflow/" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
session_prefix "" {
|
||||
policy = "write"
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
key_prefix "infra/terraform/state" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
key_prefix "infra/terraform/vault/" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
session_prefix "" {
|
||||
policy = "write"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user