Addes support for reverse proxy to home server

This commit is contained in:
2025-06-01 17:14:33 +02:00
parent 8dd48ec2c7
commit b0162abb88
25 changed files with 632 additions and 18 deletions
+5
View File
@@ -175,6 +175,11 @@ locals {
type = "CNAME"
value = "${local.vps_1.name}.alexpires.me"
},
{
name = "ai"
type = "CNAME"
value = "${local.vps_1.name}.alexpires.me"
},
]
}
+7
View File
@@ -139,3 +139,10 @@ module "ssh_locker_web" {
redirect_uri = "https://alexpires.me/api/ssh_locker/duo-callback"
socket_path = "/var/run/ssh_locker/provision.sock"
}
module "reverse_proxy" {
source = "../modules/apps/nginx-reverse-proxy"
services = local.reverse_proxy_services
wireguard_config = local.reverse_proxy_wireguard_config
}
+38
View File
@@ -77,6 +77,44 @@ locals {
}
]
#reverse proxy settings
reverse_proxy_wireguard_config = {
private_key = var.wireguard_1_private_key
public_key = var.wireguard_1_public_key
pre_shared_key = var.wireguard_1_pre_shared_key
endpoint = "aristotle.alexpires.me"
endpoint_port = 51820
address = "10.5.5.2/32"
dns = "10.19.4.1"
allowed_ips = [
"10.19.4.106/32",
]
}
reverse_proxy_services = {
"open-webui" = {
http_port = 8080
https_port = 8443
tls = true
fqdn = ["ai.alexpires.me"]
upstream = "http://10.19.4.106:8080"
locations = [
{
path = "/ws/socket.io/"
headers = {
"Upgrade" = "$http_upgrade"
"Connection" = "upgrade"
}
},
{
path = "/"
},
]
}
}
# VPS configuration
instance_ips = [
"${local.vps_1.ip}/32",
]
+1 -1
View File
@@ -19,7 +19,7 @@ terraform {
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
scaleway = {
source = "scaleway/scaleway"
+12
View File
@@ -122,3 +122,15 @@ variable "server_api_access_token" {
type = string
sensitive = true
}
variable "wireguard_1_private_key" {
description = "The private key for Wireguard"
type = string
sensitive = true
}
variable "wireguard_1_pre_shared_key" {
description = "The pre-shared key for Wireguard"
type = string
sensitive = true
}
+3
View File
@@ -31,3 +31,6 @@ TF_VAR_gitea_jwt_secret=$GITEA_JWT_SECRET
TF_VAR_server_api_duo_client_id=$SERVER_API_DUO_CLIENT_ID
TF_VAR_server_api_duo_client_secret=$SERVER_API_DUO_CLIENT_SECRET
TF_VAR_server_api_access_token=$SERVER_API_ACCESS_TOKEN
TF_VAR_wireguard_1_private_key=$WIREGUARD_1_PRIVATE_KEY
TF_VAR_wireguard_1_public_key=$WIREGUARD_1_PUBLIC_KEY
TF_VAR_wireguard_1_pre_shared_key=$WIREGUARD_1_PRE_SHARED_KEY
+6
View File
@@ -37,3 +37,9 @@ variable "mail_crypt_public_key" {
description = "Mailcrypt public key"
type = string
}
variable "wireguard_1_public_key" {
description = "The public key for Wireguard"
type = string
}
+1 -1
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
scaleway = {
source = "scaleway/scaleway"
+1 -1
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
scaleway = {
source = "scaleway/scaleway"
@@ -0,0 +1,41 @@
locals {
wireguard_version = "1.0.20210914-r4-ls54"
wireguard_config = try(templatefile("${path.module}/resources/wireguard/wireguard.conf.tpl", {
wireguard_private_key = var.wireguard_config.private_key
wireguard_public_key = var.wireguard_config.public_key
wireguard_pre_shared_key = var.wireguard_config.pre_shared_key
wireguard_address = var.wireguard_config.address
wireguard_dns = var.wireguard_config.dns
wireguard_allowed_ips = join(", ", var.wireguard_config.allowed_ips)
wireguard_endpoint = "${var.wireguard_config.endpoint}:${var.wireguard_config.endpoint_port}"
}), "null")
wireguard_config_checksum = nonsensitive(sha256(local.wireguard_config))
default_headers = {
"Host" : "$host",
"X-Real-IP" : "$remote_addr",
"X-Forwarded-For" : "$proxy_add_x_forwarded_for",
"X-Forwarded-Proto" : "$scheme",
"X-Forwarded-Host" : "$host",
"X-Forwarded-Port" : "$server_port",
}
nginx_config = file("${path.module}/resources/nginx/nginx.conf")
nginx_config_checksum = sha256(local.nginx_config)
services_config = {
for k, v in var.services : "${k}.conf" => coalesce(v.config, templatefile("${path.module}/resources/nginx/conf.d/default.conf.tpl", {
http_port = v.http_port
https_port = v.https_port
upstream = v.upstream
tls = v.tls
fqdn = join(" ", v.fqdn)
locations = [for l in v.locations : {
path = l.path
headers = merge(l.headers, coalesce(v.default_headers, local.default_headers))
}]
custom_snippet = v.custom_snippet
}))
}
services_config_checksum = sha256(jsonencode(local.services_config))
}
@@ -0,0 +1,377 @@
resource "kubernetes_namespace" "reverse_proxy" {
metadata {
annotations = {
name = "reverse-proxy"
}
labels = {
name = "reverse-proxy"
}
name = "reverse-proxy"
}
}
resource "kubernetes_service_account" "reverse-proxy_service_account" {
metadata {
name = "reverse-proxy-app-sa"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
}
automount_service_account_token = false
}
resource "kubernetes_service" "reverse-proxy" {
for_each = var.services
metadata {
name = format("reverse-proxy-%s", replace(each.key, ".", "-"))
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
}
spec {
port {
name = "http"
port = 80
target_port = substr(format("http-%s", replace(each.key, ".", "-")), 0, 15)
}
dynamic "port" {
for_each = each.value.tls ? [1] : []
content {
name = "https"
port = 443
target_port = substr(format("https-%s", replace(each.key, ".", "-")), 0, 15)
}
}
selector = {
app = "reverse-proxy"
}
cluster_ip = "None"
}
}
resource "kubernetes_manifest" "internal_cert" {
manifest = {
apiVersion = "cert-manager.io/v1"
kind = "Certificate"
metadata = {
name = "internal-cert"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
}
spec = {
secretName = "internal-cert"
issuerRef = {
name = var.issuer_name
kind = "ClusterIssuer"
}
commonName = "reverse-proxy.internal"
dnsNames = flatten([for _, v in var.services : v.fqdn if v.tls == true])
}
}
}
resource "kubernetes_config_map" "wireguard_config" {
count = local.wireguard_config != "null" ? 1 : 0
metadata {
name = "wireguard-config"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
}
data = {
"wireguard.conf" = local.wireguard_config
}
}
resource "kubernetes_config_map" "nginx_config" {
metadata {
name = "nginx-config"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
}
data = {
"nginx.conf" = local.nginx_config
}
}
resource "kubernetes_config_map" "nginx_default_config" {
metadata {
name = "nginx-default-config"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
}
data = local.services_config
}
resource "kubernetes_ingress_v1" "reverse-proxy_ingress" {
for_each = var.services
metadata {
name = "reverse-proxy-ingress"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
annotations = merge({
"kubernetes.io/ingress.class" = "public"
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
},
each.value.tls ? {
"nginx.org/ssl-services" = format("reverse-proxy-%s", replace(each.key, ".", "-"))
"nginx.org/ssl-redirect" = "true"
"nginx.org/redirect-to-https" = "true"
"nginx.ingress.kubernetes.io/backend-protocol" = "HTTPS"
} : {})
}
spec {
tls {
hosts = each.value.fqdn
secret_name = format("reverse-proxy-%s-tls", replace(each.key, ".", "-"))
}
dynamic "rule" {
for_each = each.value.fqdn
content {
host = rule.value
http {
path {
backend {
service {
name = format("reverse-proxy-%s", replace(each.key, ".", "-"))
port {
name = each.value.tls ? "https" : "http"
}
}
}
}
}
}
}
}
}
resource "kubernetes_deployment" "reverse_proxy" {
metadata {
name = "reverse-proxy"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
annotations = {
"security.alpha.kubernetes.io/unsafe-sysctls" = "net.ipv4.conf.all.src_valid_mark=1"
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "reverse-proxy"
}
}
strategy {
type = "Recreate"
}
template {
metadata {
labels = {
app = "reverse-proxy"
}
annotations = {
"checksum/nginx" = local.nginx_config_checksum
"checksum/services" = local.services_config_checksum
"checksum/wireguard" = local.wireguard_config_checksum
}
}
spec {
dynamic "container" {
for_each = local.wireguard_config != "null" ? [1] : []
content {
name = "wireguard"
image = "linuxserver/wireguard:${local.wireguard_version}"
resources {
requests = {
memory = "64Mi"
}
}
liveness_probe {
exec {
command = [
"/bin/sh",
"-c",
"wg show | grep -q transfer"
]
}
initial_delay_seconds = 90
period_seconds = 90
}
security_context {
privileged = true
capabilities {
add = ["NET_ADMIN"]
}
}
env {
name = "PUID"
value = "1000"
}
env {
name = "PGID"
value = "1000"
}
env {
name = "TZ"
value = "Europe/Berlin"
}
volume_mount {
name = "wireguard-config"
mount_path = "/config/wg_confs"
read_only = true
}
volume_mount {
name = "lib-modules"
mount_path = "/lib/modules"
read_only = true
}
}
}
container {
name = "reverse-proxy"
image = "nginx:${var.tag}"
image_pull_policy = "Always"
resources {
requests = {
memory = "100Mi"
}
}
readiness_probe {
http_get {
path = "/"
port = 8080
}
initial_delay_seconds = 5
period_seconds = 10
}
liveness_probe {
http_get {
path = "/"
port = 8080
}
initial_delay_seconds = 5
period_seconds = 10
}
security_context {
allow_privilege_escalation = false
}
dynamic "port" {
for_each = { for k, v in var.services : k => v.http_port }
content {
container_port = port.value
name = substr(format("http-%s", replace(port.key, ".", "-")), 0, 15)
}
}
dynamic "port" {
for_each = { for k, v in var.services : k => v.https_port if v.tls }
content {
container_port = port.value
name = substr(format("https-%s", replace(port.key, ".", "-")), 0, 15)
}
}
volume_mount {
name = "nginx"
mount_path = "/etc/nginx/nginx.conf"
sub_path = "nginx.conf"
read_only = true
}
volume_mount {
name = "services"
mount_path = "/etc/nginx/conf.d/"
read_only = true
}
dynamic "volume_mount" {
for_each = length([for v in var.services : v.tls if v.tls == true]) > 0 ? [1] : []
content {
name = "tls"
mount_path = "/etc/ssl/certs/private"
read_only = true
}
}
}
dynamic "volume" {
for_each = local.wireguard_config != "null" ? [1] : []
content {
name = "wireguard-config"
config_map {
name = kubernetes_config_map.wireguard_config[0].metadata[0].name
}
}
}
dynamic "volume" {
for_each = local.wireguard_config != "null" ? [1] : []
content {
name = "lib-modules"
host_path {
path = "/lib/modules"
}
}
}
volume {
name = "nginx"
config_map {
name = kubernetes_config_map.nginx_config.metadata[0].name
}
}
volume {
name = "services"
config_map {
name = kubernetes_config_map.nginx_default_config.metadata[0].name
}
}
dynamic "volume" {
for_each = length([for v in var.services : v.tls if v.tls == true]) > 0 ? [1] : []
content {
name = "tls"
secret {
secret_name = kubernetes_manifest.internal_cert.manifest["metadata"]["name"]
items {
key = "tls.crt"
path = "tls.crt"
}
items {
key = "tls.key"
path = "tls.key"
}
}
}
}
}
}
}
lifecycle {
ignore_changes = [spec[0].template[0].metadata[0].annotations["kubectl.kubernetes.io/restartedAt"]]
}
}
@@ -0,0 +1,13 @@
terraform {
required_version = "~>1.8"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "2.36.0"
}
scaleway = {
source = "scaleway/scaleway"
version = "~>2.13"
}
}
}
@@ -0,0 +1,22 @@
server {
listen ${http_port};
%{ if tls ~}
listen ${https_port} ssl;
ssl_certificate /etc/ssl/certs/private/tls.crt;
ssl_certificate_key /etc/ssl/certs/private/tls.key;
ssl_protocols TLSv1.2 TLSv1.3;
%{ endif ~}
server_name ${fqdn};
%{ if custom_snippet != "" ~}
${custom_snippet}
%{ endif ~}
%{ for l in locations ~}
location ${l.path} {
proxy_pass ${upstream};
proxy_http_version 1.1;
%{ for k,v in l.headers ~}
proxy_set_header ${k} ${v};
%{ endfor ~}
}
%{ endfor ~}
}
@@ -0,0 +1,36 @@
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
proxy_temp_path /tmp/proxy_temp;
client_body_temp_path /tmp/client_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
client_max_body_size 10M;
proxy_buffering off;
proxy_request_buffering off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
@@ -0,0 +1,10 @@
[Interface]
PrivateKey = ${wireguard_private_key}
Address = ${wireguard_address}
[Peer]
PublicKey = ${wireguard_public_key}
PresharedKey = ${wireguard_pre_shared_key}
AllowedIPs = ${wireguard_allowed_ips}
Endpoint = ${wireguard_endpoint}
PersistentKeepalive = 25
@@ -0,0 +1,45 @@
variable "tag" {
description = "The version of the nginx server to install"
type = string
default = "stable-alpine"
}
variable "wireguard_config" {
description = "The Wireguard configuration file"
type = object({
private_key = string
public_key = string
pre_shared_key = string
endpoint = string
endpoint_port = number
address = string
dns = string
allowed_ips = list(string)
})
default = null
sensitive = true
}
variable "issuer_name" {
description = "The name of the issuer to use for TLS certificates, internal certificates"
type = string
default = "internal-ca"
}
variable "services" {
description = "A list of services to be deployed"
type = map(object({
fqdn = list(string)
upstream = string
default_headers = optional(map(string), {}),
http_port = optional(number, 80)
https_port = optional(number, 443)
tls = optional(bool, false)
locations = optional(list(object({
path = string,
headers = optional(map(string), {}),
})), [])
custom_snippet = optional(string, "")
config = optional(string, "")
}))
}
-1
View File
@@ -1,7 +1,6 @@
locals {
persistent_folder = var.persistent_folder
websites_path = "${local.persistent_folder}/web"
websites = var.websites
nginx_config = file("${path.module}/resources/nginx.conf")
nginx_config_checksum = sha256(local.nginx_config)
+6 -6
View File
@@ -23,7 +23,7 @@ resource "kubernetes_service_account" "website_service_account" {
}
resource "kubernetes_service" "website" {
for_each = local.websites
for_each = var.websites
metadata {
name = format("website-%s", replace(each.key, ".", "-"))
namespace = kubernetes_namespace.website.metadata[0].name
@@ -35,7 +35,7 @@ resource "kubernetes_service" "website" {
target_port = "http"
}
dynamic "port" {
for_each = each.value.tls ? [443] : []
for_each = each.value.tls ? [1] : []
content {
name = "https"
port = 443
@@ -51,7 +51,7 @@ resource "kubernetes_service" "website" {
resource "kubernetes_manifest" "internal_cert" {
for_each = {
for k, v in local.websites : k => v if v.tls == true
for k, v in var.websites : k => v if v.tls == true
}
manifest = {
apiVersion = "cert-manager.io/v1"
@@ -84,7 +84,7 @@ resource "kubernetes_config_map" "nginx_config" {
}
resource "kubernetes_config_map" "nginx_default_config" {
for_each = local.websites
for_each = var.websites
metadata {
name = format("website-%s-default", replace(each.key, ".", "-"))
namespace = kubernetes_namespace.website.metadata[0].name
@@ -100,7 +100,7 @@ resource "kubernetes_config_map" "nginx_default_config" {
}
resource "kubernetes_ingress_v1" "website_ingress" {
for_each = local.websites
for_each = var.websites
metadata {
name = "website-ingress"
namespace = kubernetes_namespace.website.metadata[0].name
@@ -144,7 +144,7 @@ resource "kubernetes_ingress_v1" "website_ingress" {
}
resource "kubernetes_deployment" "website" {
for_each = local.websites
for_each = var.websites
metadata {
name = "website-app"
+1 -1
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
scaleway = {
source = "scaleway/scaleway"
+1 -1
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
scaleway = {
source = "scaleway/scaleway"
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
}
}
@@ -3,7 +3,7 @@ terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.18"
version = "2.36.0"
}
}
}