From 2a613ec57bcb9f00f21c232080c0283882e13a7f Mon Sep 17 00:00:00 2001 From: Matthew Baggett Date: Fri, 21 Jun 2024 16:48:12 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 2 + default.conf | 19 ++++++ default_page.tf | 19 ++++++ inputs.tf | 22 +++++++ nginx-site-available/basic-auth.tf | 11 ++++ nginx-site-available/cert.tf | 20 ++++++ nginx-site-available/config.tf | 70 ++++++++++++++++++++ nginx-site-available/inputs.tf | 67 +++++++++++++++++++ nginx-site-available/nginx_template.conf | 83 ++++++++++++++++++++++++ nginx-site-available/outputs.tf | 6 ++ nginx-site-available/terraform.tf | 22 +++++++ nginx.tf | 78 ++++++++++++++++++++++ terraform.tf | 14 ++++ 13 files changed, 433 insertions(+) create mode 100644 .gitignore create mode 100644 default.conf create mode 100644 default_page.tf create mode 100644 inputs.tf create mode 100644 nginx-site-available/basic-auth.tf create mode 100644 nginx-site-available/cert.tf create mode 100644 nginx-site-available/config.tf create mode 100644 nginx-site-available/inputs.tf create mode 100644 nginx-site-available/nginx_template.conf create mode 100644 nginx-site-available/outputs.tf create mode 100644 nginx-site-available/terraform.tf create mode 100644 nginx.tf create mode 100644 terraform.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81e4230 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/debug/* +/.idea diff --git a/default.conf b/default.conf new file mode 100644 index 0000000..a40db9f --- /dev/null +++ b/default.conf @@ -0,0 +1,19 @@ +resolver 127.0.0.11 ipv6=off valid=1s; + +server { + listen 80; + listen [::]:80; + #listen 443 ssl; + #listen [::]:443 ssl; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/default_page.tf b/default_page.tf new file mode 100644 index 0000000..649a096 --- /dev/null +++ b/default_page.tf @@ -0,0 +1,19 @@ +locals { + default_page = "

Hello, World!

" +} +resource "docker_config" "default_page" { + name = "${var.service_name}.index.html-${substr(sha1(local.default_page), 0, 4)}" + data = base64encode(local.default_page) +} +resource "local_file" "default_page" { + content = base64decode(docker_config.default_page.data) + filename = "${path.root}/.debug/nginx/index.html" +} +resource "docker_config" "default_conf" { + name = "${var.service_name}.default.conf-${substr(sha1(file("${path.module}/default.conf")), 0, 4)}" + data = base64encode(file("${path.module}/default.conf")) +} +resource "local_file" "default_conf" { + content = base64decode(docker_config.default_conf.data) + filename = "${path.root}/.debug/nginx/default.conf" +} diff --git a/inputs.tf b/inputs.tf new file mode 100644 index 0000000..9e4b5bf --- /dev/null +++ b/inputs.tf @@ -0,0 +1,22 @@ +variable "service_name" { + type = string + default = "nginx" +} +variable "configs" { + type = list(object({ + file = string + id = string + name = string + })) +} +variable "networks" { + type = list(object({ + name = string + id = string + })) +} +variable "replicas" { + type = number + default = 2 + description = "The number of instances to deploy" +} diff --git a/nginx-site-available/basic-auth.tf b/nginx-site-available/basic-auth.tf new file mode 100644 index 0000000..bbb9e71 --- /dev/null +++ b/nginx-site-available/basic-auth.tf @@ -0,0 +1,11 @@ +# Auth file +resource "docker_config" "auth" { + count = var.basic_auth != null ? 1 : 0 + name = join(".", [var.config_prefix, "auth", var.hostname, random_id.config_instance.id]) + data = base64encode(local.auth) +} +resource "local_file" "auth" { + count = var.basic_auth != null ? 1 : 0 + content = local.auth + filename = "${path.root}/.debug/nginx/${local.filenames.auth}" +} diff --git a/nginx-site-available/cert.tf b/nginx-site-available/cert.tf new file mode 100644 index 0000000..f691e86 --- /dev/null +++ b/nginx-site-available/cert.tf @@ -0,0 +1,20 @@ +resource "docker_config" "certificate" { + count = var.certificate != null ? 1 : 0 + name = join(".", [var.config_prefix, "crt", var.hostname, random_id.config_instance.id]) + data = base64encode("${var.certificate.certificate_pem}${var.certificate.issuer_pem}") +} +resource "local_file" "certificate" { + count = var.certificate != null ? 1 : 0 + content = local.cert_public + filename = "${path.root}/.debug/nginx/${local.filenames.certificate}" +} +resource "docker_config" "certificate_key" { + count = var.certificate != null ? 1 : 0 + name = join(".", [var.config_prefix, "key", var.hostname, random_id.config_instance.id]) + data = base64encode(local.cert_private) +} +resource "local_file" "certificate_key" { + count = var.certificate != null ? 1 : 0 + content = var.certificate.private_key_pem + filename = "${path.root}/.debug/nginx/${local.filenames.certificate_key}" +} diff --git a/nginx-site-available/config.tf b/nginx-site-available/config.tf new file mode 100644 index 0000000..925086a --- /dev/null +++ b/nginx-site-available/config.tf @@ -0,0 +1,70 @@ +locals { + auth = var.basic_auth != null ? "${var.basic_auth.username}:${var.basic_auth.password}" : null + config = templatefile("${path.module}/nginx_template.conf", { + hostname = var.hostname + service_name = var.service_name + http_port = var.http_port + https_port = var.https_port + upstream_host = var.upstream_host + enable_ssl = var.certificate != null + certificate = var.certificate + basic_auth = var.basic_auth + auth_file = var.basic_auth != null ? "${var.hostname}-auth.conf" : "" + allow_non_ssl = var.allow_non_ssl + redirect_non_ssl = var.redirect_non_ssl + timeout_seconds = var.timeout_seconds + host_override = var.host_override + extra_upstreams = var.extra_upstreams + extra_locations = var.extra_locations + }) + cert_public = "${var.certificate.issuer_pem}${var.certificate.certificate_pem}" + cert_private = var.certificate.private_key_pem + filenames = { + nginx = "${var.hostname}.conf" + auth = "${var.hostname}.auth" + certificate_key = "${var.hostname}.key" + certificate = "${var.hostname}.crt" + } + files = [for f in [ + { + file = local.filenames.nginx + name = docker_config.nginx_site_available.name + id = docker_config.nginx_site_available.id + }, + var.basic_auth != null ? { + file = local.filenames.auth + name = docker_config.auth[0].name + id = docker_config.auth[0].id + } : null, + var.certificate != null ? { + file = local.filenames.certificate + name = docker_config.certificate[0].name + id = docker_config.certificate[0].id + } : null, + var.certificate != null ? { + file = local.filenames.certificate_key + name = docker_config.certificate_key[0].name + id = docker_config.certificate_key[0].id + } : null + ] : f if f != null] +} + +# Nginx config +resource "random_id" "config_instance" { + byte_length = 4 + keepers = { + config : local.config, + auth : local.auth, + cert_public : local.cert_public, + cert_private : local.cert_private, + } +} +resource "docker_config" "nginx_site_available" { + name = join(".", [var.config_prefix, "conf", var.hostname, random_id.config_instance.id]) + data = base64encode(local.config) +} + +resource "local_file" "nginx_site_available" { + filename = "${path.root}/.debug/nginx/${local.filenames.nginx}" + content = local.config +} diff --git a/nginx-site-available/inputs.tf b/nginx-site-available/inputs.tf new file mode 100644 index 0000000..c3b18e7 --- /dev/null +++ b/nginx-site-available/inputs.tf @@ -0,0 +1,67 @@ +variable "hostname" { + type = string + description = "The hostname of the server" +} +variable "service_name" { + type = string + description = "The name of the service" +} +variable "upstream_host" { + type = string + description = "The host uri of the upstream server" +} +variable "certificate" { + type = object({ + private_key_pem = string + certificate_pem = string + issuer_pem = string + }) + default = null +} +variable "basic_auth" { + type = object({ + username = string + password = string + }) + default = null +} +variable "allow_non_ssl" { + type = bool + default = false +} +variable "redirect_non_ssl" { + type = bool + default = true +} +variable "timeout_seconds" { + type = number + default = 10 +} +variable "http_port" { + type = number + default = 80 +} +variable "https_port" { + type = number + default = 443 +} +variable "host_override" { + type = string + default = null +} +variable "config_prefix" { + type = string + default = "nginx" +} + +variable "extra_upstreams" { + type = list(object({ + name = string + servers = list(string) + })) + default = [] +} +variable "extra_locations" { + type = string + default = "" +} diff --git a/nginx-site-available/nginx_template.conf b/nginx-site-available/nginx_template.conf new file mode 100644 index 0000000..463cbc5 --- /dev/null +++ b/nginx-site-available/nginx_template.conf @@ -0,0 +1,83 @@ +upstream ${service_name} { + least_conn; + server ${upstream_host}; +} +%{for upstream in extra_upstreams~} +upstream ${upstream.name} { + least_conn; +%{for server in upstream.servers~} + server ${server}; +%{endfor~} +} +%{endfor~} + +%{if !allow_non_ssl~} +server { + # Redirect non-ssl to ssl + listen ${http_port}; + listen [::]:${http_port}; + server_name ${hostname}; + return 301 https://$host$request_uri; +} +%{endif~} + +server { +%{if allow_non_ssl~} + # Non-SSL Traffic is allowed + listen ${http_port~} + listen [::]:${http_port}; +%{endif~} + # SSL Traffic is allowed + listen ${https_port} ssl; + listen [::]:${https_port} ssl; + server_name ${hostname}; + access_log /var/log/nginx/${hostname}.access.log; + error_log /var/log/nginx/${hostname}.error.log; + +%{if enable_ssl~} + ssl_certificate /etc/nginx/conf.d/${hostname}.crt; + ssl_certificate_key /etc/nginx/conf.d/${hostname}.key; + # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + # ssl_ciphers HIGH:!aNULL:!MD5; +%{endif~} + + client_max_body_size 0; + + location / { +%{if host_override != null~} + proxy_set_header Host ${host_override}; +%{else~} + proxy_set_header Host $host; +%{endif~} + + # Server to send the request on to + proxy_pass http://${service_name}; + + # Standard headers setting origin data + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + +%{if basic_auth != null~} + # Http Basic Auth + auth_basic "closed site"; + auth_basic_user_file sites-enabled/${auth_file}; +%{endif~} + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_cache_bypass $http_upgrade; + proxy_buffering off; + proxy_set_header Origin ""; + + # Proxy timeouts + proxy_read_timeout ${timeout_seconds}; + proxy_connect_timeout ${timeout_seconds}; + proxy_send_timeout ${timeout_seconds}; + } + +${extra_locations} +} + diff --git a/nginx-site-available/outputs.tf b/nginx-site-available/outputs.tf new file mode 100644 index 0000000..967254f --- /dev/null +++ b/nginx-site-available/outputs.tf @@ -0,0 +1,6 @@ +output "files" { + value = local.files +} +output "hostname" { + value = var.hostname +} diff --git a/nginx-site-available/terraform.tf b/nginx-site-available/terraform.tf new file mode 100644 index 0000000..fef3a32 --- /dev/null +++ b/nginx-site-available/terraform.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>3.0" + } + random = { + source = "hashicorp/random" + version = "~>3.3" + } + local = { + source = "hashicorp/local" + version = "~>2.1" + } + scratch = { + source = "BrendanThompson/scratch" + version = "0.4.0" + } + } +} + + diff --git a/nginx.tf b/nginx.tf new file mode 100644 index 0000000..08b4cdc --- /dev/null +++ b/nginx.tf @@ -0,0 +1,78 @@ +data "docker_registry_image" "nginx" { + name = "nginx:latest" +} +resource "random_id" "iteration" { + keepers = { + configs = jsonencode(var.configs) + } + byte_length = 4 +} +resource "docker_service" "nginx" { + name = var.service_name + mode { + replicated { + replicas = var.replicas + } + } + task_spec { + container_spec { + image = "${data.docker_registry_image.nginx.name}@${data.docker_registry_image.nginx.sha256_digest}" + configs { + config_id = docker_config.default_page.id + config_name = docker_config.default_page.name + file_name = "/usr/share/nginx/html/index.html" + } + configs { + config_id = docker_config.default_conf.id + config_name = docker_config.default_conf.name + file_name = "/etc/nginx/conf.d/default.conf" + } + dynamic "configs" { + for_each = var.configs + content { + config_id = configs.value.id + config_name = configs.value.name + file_name = "/etc/nginx/conf.d/${configs.value.file}" + } + } + # Healthcheck that checks that the nginx process is running + #healthcheck { + # test = ["CMD", "pgrep", "nginx"] + # interval = "10s" + # timeout = "5s" + # retries = 3 + # start_period = "10s" + #} + healthcheck { + test = ["CMD", "true"] + } + labels { + label = "com.nginx.iteration-id" + value = random_id.iteration.hex + } + } + dynamic "networks_advanced" { + for_each = var.networks + content { + name = networks_advanced.value.id + } + } + } + endpoint_spec { + ports { + target_port = 80 + publish_mode = "ingress" + published_port = 80 + } + ports { + target_port = 443 + publish_mode = "ingress" + published_port = 443 + } + } + update_config { + parallelism = ceil(var.replicas / 3) + delay = "10s" + order = "start-first" + } +} diff --git a/terraform.tf b/terraform.tf new file mode 100644 index 0000000..4b30c7e --- /dev/null +++ b/terraform.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>3.0" + } + scratch = { + source = "BrendanThompson/scratch" + version = "0.4.0" + } + } +} + +