feat(nginx-reverse-proxy): add custom error pages and update configuration for error handling

This commit is contained in:
2025-06-07 22:10:42 +02:00
parent 01bf71f8d2
commit d969525b58
14 changed files with 445 additions and 0 deletions
@@ -24,6 +24,7 @@ locals {
services_config = { services_config = {
for k, v in var.services : "${k}.conf" => coalesce(v.config, templatefile("${path.module}/resources/nginx/conf.d/default.conf.tpl", { for k, v in var.services : "${k}.conf" => coalesce(v.config, templatefile("${path.module}/resources/nginx/conf.d/default.conf.tpl", {
errors = local.error_codes
http_port = v.http_port http_port = v.http_port
https_port = v.https_port https_port = v.https_port
upstream = v.upstream upstream = v.upstream
@@ -46,5 +47,12 @@ locals {
auth_script = file("${path.module}/resources/auth/auth_server.py") auth_script = file("${path.module}/resources/auth/auth_server.py")
auth_script_checksum = sha256(local.auth_script) auth_script_checksum = sha256(local.auth_script)
error_codes = [for f in fileset("${path.module}/resources/errors", "*.html") : split(".", f)[0]]
errors_pages = {
for f in local.error_codes :
"${f}.html" => file("${path.module}/resources/errors/${f}.html")
}
errors_pages_checksum = sha256(jsonencode(local.errors_pages))
} }
@@ -102,6 +102,19 @@ resource "kubernetes_config_map" "nginx_default_config" {
data = local.services_config data = local.services_config
} }
resource "kubernetes_config_map" "nginx_custom_50x_page" {
metadata {
name = "nginx-errors"
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
}
data = {
for k, v in local.errors_pages : k => v
}
}
resource "kubernetes_config_map" "auth_script" { resource "kubernetes_config_map" "auth_script" {
metadata { metadata {
name = "auth-script" name = "auth-script"
@@ -203,6 +216,7 @@ resource "kubernetes_deployment" "reverse_proxy" {
"checksum/services" = local.services_config_checksum "checksum/services" = local.services_config_checksum
"checksum/wireguard" = local.wireguard_config_checksum "checksum/wireguard" = local.wireguard_config_checksum
"checksum/auth_server" = local.auth_script_checksum "checksum/auth_server" = local.auth_script_checksum
"checksum/custom_50x" = local.errors_pages_checksum
} }
} }
spec { spec {
@@ -360,6 +374,12 @@ resource "kubernetes_deployment" "reverse_proxy" {
read_only = true read_only = true
} }
volume_mount {
name = "nginx-errors"
mount_path = "/usr/share/nginx/html/errors"
read_only = true
}
dynamic "volume_mount" { dynamic "volume_mount" {
for_each = length([for v in var.services : v.tls if v.tls == true]) > 0 ? [1] : [] for_each = length([for v in var.services : v.tls if v.tls == true]) > 0 ? [1] : []
content { content {
@@ -411,6 +431,13 @@ resource "kubernetes_deployment" "reverse_proxy" {
} }
} }
volume {
name = "nginx-errors"
config_map {
name = kubernetes_config_map.nginx_custom_50x_page.metadata[0].name
}
}
dynamic "volume" { dynamic "volume" {
for_each = length([for v in var.services : v.tls if v.tls == true]) > 0 ? [1] : [] for_each = length([for v in var.services : v.tls if v.tls == true]) > 0 ? [1] : []
content { content {
@@ -0,0 +1 @@
!*.html
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>400 - Bad Request</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon"></div>
<h1>400 - Bad Request</h1>
<p>Oops! The server couldn't understand your request.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>401 - Unauthorized</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">🔒</div>
<h1>401 - Unauthorized</h1>
<p>You need to log in to access this resource.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>403 - Forbidden</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">🚫</div>
<h1>403 - Forbidden</h1>
<p>You don't have permission to view this page.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404 - Page Not Found</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">🔍</div>
<h1>404 - Page Not Found</h1>
<p>We couldn't find the page you're looking for.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>408 - Request Timeout</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon"></div>
<h1>408 - Request Timeout</h1>
<p>The server took too long to respond. Try again later.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>429 - Too Many Requests</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">🚦</div>
<h1>429 - Too Many Requests</h1>
<p>You're making too many requests. Please slow down.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>500 - Internal Server Error</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">⚠️</div>
<h1>500 - Internal Server Error</h1>
<p>Something went wrong on our end. We're working on it.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>502 - Bad Gateway</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">🔌</div>
<h1>502 - Bad Gateway</h1>
<p>The server received an invalid response from upstream.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>503 - Service Unavailable</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">🛠️</div>
<h1>503 - Service Unavailable</h1>
<p>We're doing some maintenance. Please check back soon.</p>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>504 - Gateway Timeout</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
.icon {
font-size: 80px;
margin-bottom: 20px;
color: #f39c12;
}
h1 { font-size: 2rem; margin-bottom: 10px; }
p { font-size: 1rem; color: #cccccc; }
</style>
</head>
<body>
<div class="container">
<div class="icon">📡</div>
<h1>504 - Gateway Timeout</h1>
<p>The upstream server didn't respond in time.</p>
</div>
</body>
</html>
@@ -7,6 +7,14 @@ server {
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
%{ endif ~} %{ endif ~}
server_name ${fqdn}; server_name ${fqdn};
# Custom error page for upstream errors
%{ for e in errors ~}
error_page ${e} /errors/${e}.html;
location = /errors/${e}.html {
root /usr/share/nginx/html;
internal;
}
%{ endfor ~}
%{ if custom_snippet != "" ~} %{ if custom_snippet != "" ~}
${custom_snippet} ${custom_snippet}
%{ endif ~} %{ endif ~}
@@ -40,6 +48,7 @@ location = /auth {
%{ for k,v in l.headers ~} %{ for k,v in l.headers ~}
proxy_set_header ${k} ${v}; proxy_set_header ${k} ${v};
%{ endfor ~} %{ endfor ~}
proxy_intercept_errors on;
} }
%{ endfor ~} %{ endfor ~}
} }