From 5e73523c388a7a3430d9ba5a4521c0f9412d5045 Mon Sep 17 00:00:00 2001 From: Matthew Baggett <matthew@baggett.me> Date: Thu, 16 Jan 2025 19:38:54 +0100 Subject: [PATCH] Traefik basic auth + middlewares --- docker/service/debug.tf | 6 ++++ docker/service/labels.tf | 1 + docker/service/outputs.tf | 13 +++++++-- docker/service/service.tf | 4 +-- docker/service/terraform.tf | 8 +++-- docker/service/traefik.tf | 58 ++++++++++++++++++++++++++++--------- 6 files changed, 70 insertions(+), 20 deletions(-) diff --git a/docker/service/debug.tf b/docker/service/debug.tf index 61f56b6..ec96173 100644 --- a/docker/service/debug.tf +++ b/docker/service/debug.tf @@ -23,5 +23,11 @@ resource "local_file" "debug" { traefik = var.traefik placement_constraints = var.placement_constraints build_tags = local.is_build ? local.tags : [] + labels = { + computed = local.labels, + traefik = local.traefik_labels, + provided = var.labels, + final = local.merged_labels + } })) } \ No newline at end of file diff --git a/docker/service/labels.tf b/docker/service/labels.tf index d8b783d..cfaaa7c 100644 --- a/docker/service/labels.tf +++ b/docker/service/labels.tf @@ -7,4 +7,5 @@ locals { "ooo.grey.service.name" = var.service_name "ooo.grey.service.image" = local.image } + merged_labels = { for key, value in merge(local.labels, local.traefik_labels, var.labels) : key => value if value != null } } \ No newline at end of file diff --git a/docker/service/outputs.tf b/docker/service/outputs.tf index b76b1c0..a2106ca 100644 --- a/docker/service/outputs.tf +++ b/docker/service/outputs.tf @@ -13,10 +13,19 @@ output "volumes" { output "docker_service" { value = docker_service.instance } +locals { + first_auth = var.traefik.basic-auth-users != null ? "${try(var.traefik.basic-auth-users[0], null)}:${try(nonsensitive(random_password.password[var.traefik.basic-auth-users[0]].result), null)}@" : null +} output "endpoint" { value = try( - "https://${var.traefik.domain}", - "http://${docker_service.instance.name}:${docker_service.instance.endpoint_spec[0].ports[0].target_port}", + "https://${local.first_auth}${var.traefik.domain}", + "http://${local.first_auth}${docker_service.instance.name}:${docker_service.instance.endpoint_spec[0].ports[0].target_port}", null ) +} + +output "basic_auth_users" { + value = { + for user in var.traefik.basic-auth-users : user => nonsensitive(htpasswd_password.htpasswd[user].bcrypt) + } } \ No newline at end of file diff --git a/docker/service/service.tf b/docker/service/service.tf index 84185ef..4e22950 100644 --- a/docker/service/service.tf +++ b/docker/service/service.tf @@ -82,7 +82,7 @@ resource "docker_service" "instance" { # Apply the list of Container Labels dynamic "labels" { # Filter out null values - for_each = { for key, value in merge(local.labels, local.traefik_labels, var.labels) : key => value if value != null } + for_each = local.merged_labels content { label = labels.key value = labels.value @@ -188,7 +188,7 @@ resource "docker_service" "instance" { # Service Labels dynamic "labels" { - for_each = { for key, value in merge(local.labels, local.traefik_labels, var.labels) : key => value if value != null } + for_each = local.merged_labels content { label = labels.key value = labels.value diff --git a/docker/service/terraform.tf b/docker/service/terraform.tf index 37772f1..1b422ad 100644 --- a/docker/service/terraform.tf +++ b/docker/service/terraform.tf @@ -4,11 +4,15 @@ terraform { required_providers { docker = { source = "kreuzwerker/docker" - version = "~>3.0" + version = "~> 3.0" } local = { source = "hashicorp/local" - version = "~>2.1" + version = "~> 2.1" + } + htpasswd = { + source = "loafoe/htpasswd" + version = "~> 1.0" } } } diff --git a/docker/service/traefik.tf b/docker/service/traefik.tf index 22d9f28..d53cf59 100644 --- a/docker/service/traefik.tf +++ b/docker/service/traefik.tf @@ -1,19 +1,33 @@ variable "traefik" { default = null type = object({ - domain = string - port = optional(number) - non-ssl = optional(bool, true) - ssl = optional(bool, false) - rule = optional(string) - middlewares = optional(list(string)) - network = optional(object({ - name = string - id = string - })) + domain = string + port = optional(number) + non-ssl = optional(bool, true) + ssl = optional(bool, false) + rule = optional(string) + middlewares = optional(list(string)) + network = optional(object({ name = string, id = string })) + basic-auth-users = optional(list(string)) }) description = "Whether to enable traefik for the service." } +resource "random_password" "password" { + for_each = toset(var.traefik.basic-auth-users) + length = 16 + special = false +} +resource "random_password" "salt" { + for_each = toset(var.traefik.basic-auth-users) + length = 8 + special = true + override_special = "!@#%&*()-_=+[]{}<>:?" +} +resource "htpasswd_password" "htpasswd" { + for_each = toset(var.traefik.basic-auth-users) + password = random_password.password[each.key].result + salt = random_password.salt[each.key].result +} locals { is_traefik = var.traefik != null # Calculate the traefik labels to use if enabled @@ -21,6 +35,21 @@ locals { substr(var.stack_name, 0, 20), substr(var.service_name, 0, 63 - 1 - 20), ]) + traefik_basic_auth = ( + local.is_traefik + ? ( + var.traefik.basic-auth-users != null + ? { + "traefik.http.middlewares.${local.traefik_service}-auth.basicauth.users" = join(",", [ + for user in var.traefik.basic-auth-users : "${user}:${htpasswd_password.htpasswd[user].bcrypt}" + ]) + } + : {} + ) : {} + ) + traefik_middlewares = concat(coalesce(var.traefik.middlewares, []), [ + local.traefik_basic_auth != null ? "${local.traefik_service}-auth" : null + ]) traefik_rule = ( local.is_traefik ? ( @@ -51,12 +80,13 @@ locals { "traefik.http.services.${local.traefik_service}-ssl.loadbalancer.passhostheader" = var.traefik.ssl ? "true" : null "traefik.http.services.${local.traefik_service}-ssl.loadbalancer.server.port" = var.traefik.ssl ? var.traefik.port : null }, - (var.traefik.middlewares != null + (local.traefik_middlewares != null ? { - "traefik.http.routers.${local.traefik_service}.middlewares" = join(",", var.traefik.middlewares) - "traefik.http.routers.${local.traefik_service}-ssl.middlewares" = join(",", var.traefik.middlewares) + "traefik.http.routers.${local.traefik_service}.middlewares" = join(",", local.traefik_middlewares) + "traefik.http.routers.${local.traefik_service}-ssl.middlewares" = join(",", local.traefik_middlewares) } : {} - ) + ), + local.traefik_basic_auth, ) : {}) }