From 787887da2c83ad08d77e648dd0749d41104b75db Mon Sep 17 00:00:00 2001 From: Matthew Baggett <matthew@baggett.me> Date: Fri, 14 Feb 2025 21:31:35 +0100 Subject: [PATCH] Secrets support --- docker/secret/config.tf | 18 ++++++++++++++++++ docker/secret/debug.tf | 14 ++++++++++++++ docker/secret/inputs.tf | 22 ++++++++++++++++++++++ docker/secret/locals.tf | 24 ++++++++++++++++++++++++ docker/secret/outputs.tf | 6 ++++++ docker/secret/terraform.tf | 20 ++++++++++++++++++++ docker/service/inputs.tf | 5 +++++ docker/service/secrets.tf | 8 ++++++++ docker/service/service.tf | 9 +++++++++ 9 files changed, 126 insertions(+) create mode 100644 docker/secret/config.tf create mode 100644 docker/secret/debug.tf create mode 100644 docker/secret/inputs.tf create mode 100644 docker/secret/locals.tf create mode 100644 docker/secret/outputs.tf create mode 100644 docker/secret/terraform.tf create mode 100644 docker/service/secrets.tf diff --git a/docker/secret/config.tf b/docker/secret/config.tf new file mode 100644 index 0000000..ff57260 --- /dev/null +++ b/docker/secret/config.tf @@ -0,0 +1,18 @@ +resource "random_id" "randomiser" { + byte_length = 2 + keepers = { + stack_name = var.stack_name + data = var.value + } +} + +resource "docker_secret" "secret" { + name = local.secret_name + data = base64encode(var.value) + + lifecycle { + create_before_destroy = true + ignore_changes = [name] + } +} + diff --git a/docker/secret/debug.tf b/docker/secret/debug.tf new file mode 100644 index 0000000..3964912 --- /dev/null +++ b/docker/secret/debug.tf @@ -0,0 +1,14 @@ +resource "local_file" "secret" { + count = var.debug ? 1 : 0 + content = var.value + filename = "${local.debug_path}/${local.file_name}" + file_permission = "0600" +} +variable "debug_path" { + type = string + description = "Path to write debug files to" + default = null +} +locals { + debug_path = var.debug_path != null ? var.debug_path : "${path.root}/.debug/docker/secrets/${var.stack_name}" +} \ No newline at end of file diff --git a/docker/secret/inputs.tf b/docker/secret/inputs.tf new file mode 100644 index 0000000..0457836 --- /dev/null +++ b/docker/secret/inputs.tf @@ -0,0 +1,22 @@ +variable "stack_name" { + type = string + description = "The name of the stack to deploy the service to." +} +variable "name" { + type = string + description = "The name of the docker secret." +} +variable "value" { + type = string + description = "The value of the docker secret." +} +variable "labels" { + type = map(string) + default = {} + description = "A map of labels to apply to the service" +} +variable "debug" { + type = bool + default = true + description = "Emit debug files in .debug directory" +} \ No newline at end of file diff --git a/docker/secret/locals.tf b/docker/secret/locals.tf new file mode 100644 index 0000000..c1ce909 --- /dev/null +++ b/docker/secret/locals.tf @@ -0,0 +1,24 @@ +locals { + path = var.name + file_name = element(split("/", local.path), length(split("/", local.path)) - 1) + // Name can be 64 bytes long, including a null byte seemingly, limiting the length to 63. + // The hash is 7 bytes long. We lose 2 more bytes to the dashes. So we have 54 bytes left. + // I will share that into 20 bytes for the stack name, remaining bytes for the config name + secret_name = join("-", [ + substr(var.stack_name, 0, 20), + substr(local.file_name, 0, 64 - 20 - 1 - (random_id.randomiser.byte_length * 2) - 1), + random_id.randomiser.hex + ]) + + // define secret labels + labels = merge(var.labels, { + "com.docker.stack.namespace" = var.stack_name + "ooo.grey.secret.stack" = var.stack_name + #"ooo.grey.secret.created" = plantimestamp() + "ooo.grey.secret.bytes" = length(var.value) + "ooo.grey.secret.name" = local.secret_name + "ooo.grey.secret.hash" = sha1(var.value) + "ooo.grey.secret.file" = local.file_name + "ooo.grey.secret.path" = local.path + }) +} \ No newline at end of file diff --git a/docker/secret/outputs.tf b/docker/secret/outputs.tf new file mode 100644 index 0000000..e2d0206 --- /dev/null +++ b/docker/secret/outputs.tf @@ -0,0 +1,6 @@ +output "id" { + value = docker_secret.secret.id +} +output "name" { + value = docker_secret.secret.name +} \ No newline at end of file diff --git a/docker/secret/terraform.tf b/docker/secret/terraform.tf new file mode 100644 index 0000000..26d0fe2 --- /dev/null +++ b/docker/secret/terraform.tf @@ -0,0 +1,20 @@ +terraform { + required_version = "~> 1.6" + + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>3.0" + } + local = { + source = "hashicorp/local" + version = "~>2.1" + } + random = { + source = "hashicorp/random" + version = "~>3.5" + } + } +} + + diff --git a/docker/service/inputs.tf b/docker/service/inputs.tf index 0e1123a..a73b4a4 100644 --- a/docker/service/inputs.tf +++ b/docker/service/inputs.tf @@ -110,6 +110,11 @@ variable "mounts" { default = {} description = "A map of host paths to container paths to mount. The key is the host path, and the value is the container path." } +variable "secrets" { + type = map(string) + default = {} + description = "A map of secret files to create. Key being the path to the file, and the value being the content. The secret will be created using the truncated file name and a timestamp." +} variable "configs" { type = map(string) default = {} diff --git a/docker/service/secrets.tf b/docker/service/secrets.tf new file mode 100644 index 0000000..e1b4496 --- /dev/null +++ b/docker/service/secrets.tf @@ -0,0 +1,8 @@ +module "secrets" { + for_each = var.secrets + source = "../../docker/secret" + stack_name = var.stack_name + name = each.key + value = each.value + debug_path = "${local.debug_path}/secrets" +} \ No newline at end of file diff --git a/docker/service/service.tf b/docker/service/service.tf index a3d16a3..16e2306 100644 --- a/docker/service/service.tf +++ b/docker/service/service.tf @@ -61,6 +61,15 @@ resource "docker_service" "instance" { file_name = configs.key } } + # Same for secrets + dynamic "secrets" { + for_each = var.secrets + content { + secret_id = module.secrets[secrets.key].id + secret_name = module.secrets[secrets.key].name + file_name = secrets.key + } + } # Allow overriding DNS server in use dynamic "dns_config" {