[ci skip] Infrastructure hardening: security, monitoring, reliability, maintainability
Phase 1 - Critical Security: - Netbox: move hardcoded DB/superuser passwords to variables - MeshCentral: disable public registration, add Authentik auth - Traefik: disable insecure API dashboard (api.insecure=false) - Traefik: configure forwarded headers with Cloudflare trusted IPs Phase 2 - Security Hardening: - Add security headers middleware (HSTS, X-Frame-Options, nosniff, etc.) - Add Kyverno pod security policies in audit mode (privileged, host namespaces, SYS_ADMIN, trusted registries) - Tighten rate limiting (avg=10, burst=50) - Add Authentik protection to grampsweb Phase 3 - Monitoring & Alerting: - Add critical service alerts (PostgreSQL, MySQL, Redis, Headscale, Authentik, Loki) - Increase Loki retention from 7 to 30 days (720h) - Add predictive PV filling alert (predict_linear) - Re-enable Hackmd and Privatebin down alerts Phase 4 - Reliability: - Add resource requests/limits to Redis, DBaaS, Technitium, Headscale, Vaultwarden, Uptime Kuma - Increase Alloy DaemonSet memory to 512Mi/1Gi Phase 6 - Maintainability: - Extract duplicated tiers locals to terragrunt.hcl generate block (removed from 67 stacks) - Replace hardcoded NFS IP 10.0.10.15 with var.nfs_server (114 instances across 63 files) - Replace hardcoded Redis/PostgreSQL/MySQL/Ollama/mail host references with variables across ~35 stacks - Migrate xray raw ingress resources to ingress_factory modules
This commit is contained in:
parent
1b4737c90c
commit
89a6e08245
104 changed files with 773 additions and 920 deletions
|
|
@ -2,6 +2,7 @@ variable "tls_secret_name" {}
|
|||
variable "secret_key" {}
|
||||
variable "postgres_password" {}
|
||||
variable "tier" { type = string }
|
||||
variable "redis_host" { type = string }
|
||||
|
||||
|
||||
module "tls_secret" {
|
||||
|
|
@ -48,7 +49,7 @@ resource "helm_release" "authentik" {
|
|||
atomic = true
|
||||
timeout = 6000
|
||||
|
||||
values = [templatefile("${path.module}/values.yaml", { postgres_password = var.postgres_password, secret_key = var.secret_key })]
|
||||
values = [templatefile("${path.module}/values.yaml", { postgres_password = var.postgres_password, secret_key = var.secret_key, redis_host = var.redis_host })]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ authentik:
|
|||
user: authentik
|
||||
password: ${postgres_password}
|
||||
redis:
|
||||
host: redis.redis
|
||||
host: ${redis_host}
|
||||
|
||||
server:
|
||||
replicas: 3
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ variable "crowdsec_dash_machine_id" { type = string } # used for web dash
|
|||
variable "crowdsec_dash_machine_password" { type = string } # used for web dash
|
||||
variable "tier" { type = string }
|
||||
variable "slack_webhook_url" { type = string }
|
||||
variable "mysql_host" { type = string }
|
||||
|
||||
module "tls_secret" {
|
||||
source = "../../../../modules/kubernetes/setup_tls_secret"
|
||||
|
|
@ -99,7 +100,7 @@ resource "helm_release" "crowdsec" {
|
|||
repository = "https://crowdsecurity.github.io/helm-charts"
|
||||
chart = "crowdsec"
|
||||
|
||||
values = [templatefile("${path.module}/values.yaml", { homepage_username = var.homepage_username, homepage_password = var.homepage_password, DB_PASSWORD = var.db_password, ENROLL_KEY = var.enroll_key, SLACK_WEBHOOK_URL = var.slack_webhook_url })]
|
||||
values = [templatefile("${path.module}/values.yaml", { homepage_username = var.homepage_username, homepage_password = var.homepage_password, DB_PASSWORD = var.db_password, ENROLL_KEY = var.enroll_key, SLACK_WEBHOOK_URL = var.slack_webhook_url, mysql_host = var.mysql_host })]
|
||||
timeout = 3600
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ lapi:
|
|||
- name: MB_DB_PASS
|
||||
value: "${DB_PASSWORD}"
|
||||
- name: MB_DB_HOST
|
||||
value: "mysql.dbaas.svc.cluster.local"
|
||||
value: "${mysql_host}"
|
||||
|
||||
- name: MB_EMAIL_SMTP_USERNAME
|
||||
value: "info@viktorbarzin.me"
|
||||
|
|
@ -166,7 +166,7 @@ config:
|
|||
user: crowdsec
|
||||
password: ${DB_PASSWORD}
|
||||
db_name: crowdsec
|
||||
host: mysql.dbaas.svc.cluster.local
|
||||
host: ${mysql_host}
|
||||
port: 3306
|
||||
api:
|
||||
server:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ variable "prod" {
|
|||
default = false
|
||||
type = bool
|
||||
}
|
||||
variable "nfs_server" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "dbaas" {
|
||||
metadata {
|
||||
|
|
@ -131,6 +132,18 @@ resource "kubernetes_deployment" "mysql" {
|
|||
container {
|
||||
image = "mysql:9.2.0"
|
||||
name = "mysql"
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "250m"
|
||||
memory = "512Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "1"
|
||||
memory = "2Gi"
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MYSQL_ROOT_PASSWORD"
|
||||
value = var.dbaas_root_password
|
||||
|
|
@ -153,7 +166,7 @@ resource "kubernetes_deployment" "mysql" {
|
|||
name = "mysql-persistent-storage"
|
||||
nfs {
|
||||
path = "/mnt/main/mysql"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +232,7 @@ resource "kubernetes_cron_job_v1" "mysql-backup" {
|
|||
name = "mysql-backup"
|
||||
nfs {
|
||||
path = "/mnt/main/mysql-backup"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -717,6 +730,18 @@ resource "kubernetes_deployment" "postgres" {
|
|||
image = "viktorbarzin/postgres:16-master" # mix of postgis + pgvector
|
||||
# image = "postgres:17.2-bullseye" # needs pg_upgrade to data dir
|
||||
name = "postgresql"
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "250m"
|
||||
memory = "512Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "1"
|
||||
memory = "2Gi"
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "POSTGRES_PASSWORD"
|
||||
value = var.postgresql_root_password
|
||||
|
|
@ -744,7 +769,7 @@ resource "kubernetes_deployment" "postgres" {
|
|||
name = "postgresql-persistent-storage"
|
||||
nfs {
|
||||
path = "/mnt/main/postgresql/data"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
# volume {
|
||||
|
|
@ -830,7 +855,7 @@ resource "kubernetes_deployment" "pgadmin" {
|
|||
# }
|
||||
nfs {
|
||||
path = "/mnt/main/postgresql/pgadmin"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -905,7 +930,7 @@ resource "kubernetes_cron_job_v1" "postgresql-backup" {
|
|||
name = "postgresql-backup"
|
||||
nfs {
|
||||
path = "/mnt/main/postgresql-backup"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ variable "tls_secret_name" {}
|
|||
variable "tier" { type = string }
|
||||
variable "headscale_config" {}
|
||||
variable "headscale_acl" {}
|
||||
variable "nfs_server" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "headscale" {
|
||||
metadata {
|
||||
|
|
@ -61,6 +62,18 @@ resource "kubernetes_deployment" "headscale" {
|
|||
# image = "headscale/headscale:0.23.0-debug" # -debug is for debug images
|
||||
name = "headscale"
|
||||
command = ["headscale", "serve"]
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "50m"
|
||||
memory = "64Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "200m"
|
||||
memory = "256Mi"
|
||||
}
|
||||
}
|
||||
|
||||
port {
|
||||
container_port = 8080
|
||||
}
|
||||
|
|
@ -100,7 +113,7 @@ resource "kubernetes_deployment" "headscale" {
|
|||
name = "nfs-config"
|
||||
nfs {
|
||||
path = "/mnt/main/headscale"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
# container {
|
||||
|
|
@ -114,6 +127,18 @@ resource "kubernetes_deployment" "headscale" {
|
|||
image = "ghcr.io/gurucomputing/headscale-ui:latest"
|
||||
# image = "ghcr.io/tale/headplane:0.3.2"
|
||||
name = "headscale-ui"
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "25m"
|
||||
memory = "32Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "100m"
|
||||
memory = "128Mi"
|
||||
}
|
||||
}
|
||||
|
||||
port {
|
||||
container_port = 8081
|
||||
# container_port = 3000
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ variable "git_user" {}
|
|||
variable "git_token" {}
|
||||
variable "technitium_username" {}
|
||||
variable "technitium_password" {}
|
||||
variable "nfs_server" { type = string }
|
||||
|
||||
|
||||
# DISABLED WHILST USING CLOUDFLARE NS
|
||||
|
|
@ -124,7 +125,7 @@ resource "kubernetes_cron_job_v1" "backup-etcd" {
|
|||
name = "backup"
|
||||
nfs {
|
||||
path = "/mnt/main/etcd-backup"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
volume {
|
||||
|
|
|
|||
203
stacks/platform/modules/kyverno/security-policies.tf
Normal file
203
stacks/platform/modules/kyverno/security-policies.tf
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
# =============================================================================
|
||||
# Pod Security Policies (Audit Mode)
|
||||
# =============================================================================
|
||||
# Kyverno validate policies for pod security standards.
|
||||
# All policies start in Audit mode - violations are logged but not blocked.
|
||||
|
||||
resource "kubernetes_manifest" "policy_deny_privileged" {
|
||||
manifest = {
|
||||
apiVersion = "kyverno.io/v1"
|
||||
kind = "ClusterPolicy"
|
||||
metadata = {
|
||||
name = "deny-privileged-containers"
|
||||
annotations = {
|
||||
"policies.kyverno.io/title" = "Deny Privileged Containers"
|
||||
"policies.kyverno.io/category" = "Pod Security"
|
||||
"policies.kyverno.io/severity" = "high"
|
||||
"policies.kyverno.io/description" = "Privileged containers have full host access. Deny unless explicitly exempted."
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
validationFailureAction = "Audit"
|
||||
background = true
|
||||
rules = [{
|
||||
name = "deny-privileged"
|
||||
match = {
|
||||
any = [{
|
||||
resources = {
|
||||
kinds = ["Pod"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
exclude = {
|
||||
any = [{
|
||||
resources = {
|
||||
namespaces = ["frigate", "nvidia", "monitoring"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
validate = {
|
||||
message = "Privileged containers are not allowed. Use specific capabilities instead."
|
||||
pattern = {
|
||||
spec = {
|
||||
containers = [{
|
||||
"=(securityContext)" = {
|
||||
"=(privileged)" = false
|
||||
}
|
||||
}]
|
||||
"=(initContainers)" = [{
|
||||
"=(securityContext)" = {
|
||||
"=(privileged)" = false
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [helm_release.kyverno]
|
||||
}
|
||||
|
||||
resource "kubernetes_manifest" "policy_deny_host_namespaces" {
|
||||
manifest = {
|
||||
apiVersion = "kyverno.io/v1"
|
||||
kind = "ClusterPolicy"
|
||||
metadata = {
|
||||
name = "deny-host-namespaces"
|
||||
annotations = {
|
||||
"policies.kyverno.io/title" = "Deny Host Namespaces"
|
||||
"policies.kyverno.io/category" = "Pod Security"
|
||||
"policies.kyverno.io/severity" = "high"
|
||||
"policies.kyverno.io/description" = "Sharing host namespaces enables container escapes. Deny hostNetwork, hostPID, hostIPC."
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
validationFailureAction = "Audit"
|
||||
background = true
|
||||
rules = [{
|
||||
name = "deny-host-namespaces"
|
||||
match = {
|
||||
any = [{
|
||||
resources = {
|
||||
kinds = ["Pod"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
exclude = {
|
||||
any = [{
|
||||
resources = {
|
||||
namespaces = ["frigate", "monitoring"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
validate = {
|
||||
message = "Host namespaces (hostNetwork, hostPID, hostIPC) are not allowed."
|
||||
pattern = {
|
||||
spec = {
|
||||
"=(hostNetwork)" = false
|
||||
"=(hostPID)" = false
|
||||
"=(hostIPC)" = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [helm_release.kyverno]
|
||||
}
|
||||
|
||||
resource "kubernetes_manifest" "policy_restrict_capabilities" {
|
||||
manifest = {
|
||||
apiVersion = "kyverno.io/v1"
|
||||
kind = "ClusterPolicy"
|
||||
metadata = {
|
||||
name = "restrict-sys-admin"
|
||||
annotations = {
|
||||
"policies.kyverno.io/title" = "Restrict SYS_ADMIN Capability"
|
||||
"policies.kyverno.io/category" = "Pod Security"
|
||||
"policies.kyverno.io/severity" = "high"
|
||||
"policies.kyverno.io/description" = "SYS_ADMIN is nearly equivalent to root. Restrict to explicitly exempted namespaces."
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
validationFailureAction = "Audit"
|
||||
background = true
|
||||
rules = [{
|
||||
name = "restrict-sys-admin"
|
||||
match = {
|
||||
any = [{
|
||||
resources = {
|
||||
kinds = ["Pod"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
exclude = {
|
||||
any = [{
|
||||
resources = {
|
||||
namespaces = ["nvidia", "monitoring"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
validate = {
|
||||
message = "Adding SYS_ADMIN capability is not allowed."
|
||||
deny = {
|
||||
conditions = {
|
||||
any = [{
|
||||
key = "{{ request.object.spec.containers[].securityContext.capabilities.add[] || `[]` }}"
|
||||
operator = "AnyIn"
|
||||
value = ["SYS_ADMIN"]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [helm_release.kyverno]
|
||||
}
|
||||
|
||||
resource "kubernetes_manifest" "policy_require_trusted_registries" {
|
||||
manifest = {
|
||||
apiVersion = "kyverno.io/v1"
|
||||
kind = "ClusterPolicy"
|
||||
metadata = {
|
||||
name = "require-trusted-registries"
|
||||
annotations = {
|
||||
"policies.kyverno.io/title" = "Require Trusted Image Registries"
|
||||
"policies.kyverno.io/category" = "Pod Security"
|
||||
"policies.kyverno.io/severity" = "medium"
|
||||
"policies.kyverno.io/description" = "Images must come from trusted registries to prevent supply chain attacks."
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
validationFailureAction = "Audit"
|
||||
background = true
|
||||
rules = [{
|
||||
name = "validate-registries"
|
||||
match = {
|
||||
any = [{
|
||||
resources = {
|
||||
kinds = ["Pod"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
validate = {
|
||||
message = "Images must be from trusted registries (docker.io, ghcr.io, quay.io, registry.k8s.io, or local cache)."
|
||||
pattern = {
|
||||
spec = {
|
||||
containers = [{
|
||||
image = "docker.io/* | ghcr.io/* | quay.io/* | registry.k8s.io/* | 10.0.20.10* | */*"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [helm_release.kyverno]
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ variable "mailserver_accounts" {}
|
|||
variable "postfix_account_aliases" {}
|
||||
variable "opendkim_key" {}
|
||||
variable "sasl_passwd" {} # For sendgrid i.e relayhost
|
||||
variable "nfs_server" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "mailserver" {
|
||||
metadata {
|
||||
|
|
@ -106,7 +107,7 @@ resource "kubernetes_config_map" "mailserver_config" {
|
|||
}
|
||||
}
|
||||
EOF
|
||||
fail2ban_conf = <<-EOF
|
||||
fail2ban_conf = <<-EOF
|
||||
[DEFAULT]
|
||||
|
||||
#logtarget = /var/log/fail2ban.log
|
||||
|
|
@ -393,7 +394,7 @@ resource "kubernetes_deployment" "mailserver" {
|
|||
name = "data"
|
||||
nfs {
|
||||
path = "/mnt/main/mailserver"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
# iscsi {
|
||||
# target_portal = "iscsi.viktorbarzin.lan:3260"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
variable "roundcube_db_password" { type = string }
|
||||
variable "mysql_host" { type = string }
|
||||
|
||||
# If you want to override settings mount this in /var/roundcube/config
|
||||
# more info in https://github.com/roundcube/roundcubemail-docker?tab=readme-ov-file
|
||||
|
|
@ -89,7 +90,7 @@ resource "kubernetes_deployment" "roundcubemail" {
|
|||
}
|
||||
env {
|
||||
name = "ROUNDCUBEMAIL_DB_HOST"
|
||||
value = "mysql.dbaas"
|
||||
value = var.mysql_host
|
||||
}
|
||||
env {
|
||||
name = "ROUNDCUBEMAIL_DB_USER"
|
||||
|
|
@ -148,14 +149,14 @@ resource "kubernetes_deployment" "roundcubemail" {
|
|||
name = "html"
|
||||
nfs {
|
||||
path = "/mnt/main/roundcubemail/html"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
volume {
|
||||
name = "enigma"
|
||||
nfs {
|
||||
path = "/mnt/main/roundcubemail/enigma"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ alloy:
|
|||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 256Mi
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 768Mi
|
||||
memory: 1Gi
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
# resource "kubernetes_persistent_volume" "prometheus_grafana_pv" {
|
||||
# metadata {
|
||||
# name = "grafana-pv"
|
||||
|
|
@ -11,7 +12,7 @@
|
|||
# persistent_volume_source {
|
||||
# nfs {
|
||||
# path = "/mnt/main/grafana"
|
||||
# server = "10.0.10.15"
|
||||
# server = var.nfs_server
|
||||
# }
|
||||
# # iscsi {
|
||||
# # target_portal = "iscsi.viktorbarzin.lan:3260"
|
||||
|
|
@ -35,7 +36,7 @@ resource "kubernetes_persistent_volume" "alertmanager_pv" {
|
|||
persistent_volume_source {
|
||||
nfs {
|
||||
path = "/mnt/main/alertmanager"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -65,5 +66,5 @@ resource "helm_release" "grafana" {
|
|||
repository = "https://grafana.github.io/helm-charts"
|
||||
chart = "grafana"
|
||||
|
||||
values = [templatefile("${path.module}/grafana_chart_values.yaml", { db_password = var.grafana_db_password, grafana_admin_password = var.grafana_admin_password })]
|
||||
values = [templatefile("${path.module}/grafana_chart_values.yaml", { db_password = var.grafana_db_password, grafana_admin_password = var.grafana_admin_password, mysql_host = var.mysql_host })]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ env:
|
|||
grafana.ini:
|
||||
database:
|
||||
type: mysql
|
||||
host: mysql.dbaas.svc.cluster.local:3306
|
||||
host: ${mysql_host}:3306
|
||||
name: grafana
|
||||
user: grafana
|
||||
password: $__env{GF_DATABASE_PASSWORD}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
variable "nfs_server" { type = string }
|
||||
|
||||
resource "helm_release" "loki" {
|
||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
||||
create_namespace = true
|
||||
|
|
@ -24,7 +26,7 @@ resource "kubernetes_persistent_volume" "loki" {
|
|||
persistent_volume_source {
|
||||
nfs {
|
||||
path = "/mnt/main/loki/loki"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
persistent_volume_reclaim_policy = "Retain"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ loki:
|
|||
limits_config:
|
||||
allow_structured_metadata: true
|
||||
volume_enabled: true
|
||||
retention_period: 168h
|
||||
retention_period: 720h
|
||||
compactor:
|
||||
retention_enabled: true
|
||||
working_directory: /var/loki/compactor
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ variable "pve_password" { type = string }
|
|||
variable "grafana_db_password" { type = string }
|
||||
variable "grafana_admin_password" { type = string }
|
||||
variable "tier" { type = string }
|
||||
variable "mysql_host" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "monitoring" {
|
||||
metadata {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
resource "kubernetes_persistent_volume_claim" "prometheus_server_pvc" {
|
||||
metadata {
|
||||
name = "prometheus-iscsi-pvc"
|
||||
|
|
@ -29,7 +30,7 @@ resource "kubernetes_persistent_volume" "prometheus_server_pvc" {
|
|||
persistent_volume_source {
|
||||
nfs {
|
||||
path = "/mnt/main/prometheus"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
# iscsi {
|
||||
# fs_type = "ext4"
|
||||
|
|
|
|||
|
|
@ -316,6 +316,13 @@ serverFiles:
|
|||
severity: warning
|
||||
annotations:
|
||||
summary: "PV {{ $labels.persistentvolumeclaim }} in {{ $labels.namespace }}: {{ $value | printf \"%.0f\" }}% used (threshold: 85%)"
|
||||
- alert: PVPredictedFull
|
||||
expr: predict_linear(kubelet_volume_stats_used_bytes[6h], 3600*24) > kubelet_volume_stats_capacity_bytes
|
||||
for: 1h
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "PV {{ $labels.persistentvolumeclaim }} in {{ $labels.namespace }} predicted to fill within 24h"
|
||||
- name: K8s Health
|
||||
rules:
|
||||
- alert: PodCrashLooping
|
||||
|
|
@ -389,6 +396,50 @@ serverFiles:
|
|||
severity: warning
|
||||
annotations:
|
||||
summary: "Prometheus notification errors: {{ $value | printf \"%.2f\" }}/s"
|
||||
- name: Critical Services
|
||||
rules:
|
||||
- alert: PostgreSQLDown
|
||||
expr: (kube_deployment_status_replicas_available{namespace="dbaas", deployment=~"postgresql.*"} or on() vector(0)) < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "PostgreSQL has no available replicas"
|
||||
- alert: MySQLDown
|
||||
expr: (kube_deployment_status_replicas_available{namespace="dbaas", deployment=~"mysql.*"} or on() vector(0)) < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "MySQL has no available replicas"
|
||||
- alert: RedisDown
|
||||
expr: (kube_deployment_status_replicas_available{namespace="redis"} or on() vector(0)) < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Redis has no available replicas"
|
||||
- alert: HeadscaleDown
|
||||
expr: (kube_deployment_status_replicas_available{namespace="headscale"} or on() vector(0)) < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Headscale VPN has no available replicas"
|
||||
- alert: AuthentikDown
|
||||
expr: (kube_deployment_status_replicas_available{namespace="authentik", deployment="authentik-server"} or on() vector(0)) < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Authentik auth server has no available replicas"
|
||||
- alert: LokiDown
|
||||
expr: (kube_statefulset_status_replicas_ready{namespace="monitoring", statefulset=~"loki.*"} or on() vector(0)) < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Loki log aggregation has no ready replicas"
|
||||
- name: Cluster
|
||||
rules:
|
||||
- alert: NodeDown
|
||||
|
|
@ -548,20 +599,20 @@ serverFiles:
|
|||
severity: page
|
||||
annotations:
|
||||
summary: Mail server has no available replicas. This means mail may not be received.
|
||||
# - alert: Hackmd has no replicas available
|
||||
# expr: (kube_deployment_status_replicas_available{namespace="hackmd"} or on() vector(0)) < 1
|
||||
# for: 1m
|
||||
# labels:
|
||||
# severity: page
|
||||
# annotations:
|
||||
# summary: Hackmd has no available replicas.
|
||||
# - alert: Privatebin has no replicas available
|
||||
# expr: (kube_deployment_status_replicas_available{namespace="privatebin"} or on() vector(0)) < 1
|
||||
# for: 10m
|
||||
# labels:
|
||||
# severity: page
|
||||
# annotations:
|
||||
# summary: Privatebin has no available replicas.
|
||||
- alert: HackmdDown
|
||||
expr: (kube_deployment_status_replicas_available{namespace="hackmd"} or on() vector(0)) < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Hackmd has no available replicas"
|
||||
- alert: PrivatebinDown
|
||||
expr: (kube_deployment_status_replicas_available{namespace="privatebin"} or on() vector(0)) < 1
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Privatebin has no available replicas"
|
||||
# - name: London OpenWRT Down
|
||||
# rules:
|
||||
# - alert: OpenWRT client unreachable
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ resource "kubernetes_namespace" "nvidia" {
|
|||
name = "nvidia"
|
||||
labels = {
|
||||
"istio-injection" : "disabled"
|
||||
tier = var.tier
|
||||
tier = var.tier
|
||||
"resource-governance/custom-quota" = "true"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
variable "tls_secret_name" {}
|
||||
variable "tier" { type = string }
|
||||
variable "nfs_server" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "redis" {
|
||||
metadata {
|
||||
|
|
@ -49,6 +50,17 @@ resource "kubernetes_deployment" "redis" {
|
|||
image = "redis/redis-stack:latest"
|
||||
name = "redis"
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "100m"
|
||||
memory = "128Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "500m"
|
||||
memory = "512Mi"
|
||||
}
|
||||
}
|
||||
|
||||
port {
|
||||
container_port = 6379
|
||||
}
|
||||
|
|
@ -64,7 +76,7 @@ resource "kubernetes_deployment" "redis" {
|
|||
name = "data"
|
||||
nfs {
|
||||
path = "/mnt/main/redis"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ variable "tls_secret_name" {}
|
|||
variable "tier" { type = string }
|
||||
variable "homepage_token" {}
|
||||
variable "technitium_db_password" {}
|
||||
variable "nfs_server" { type = string }
|
||||
variable "mysql_host" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "technitium" {
|
||||
metadata {
|
||||
|
|
@ -131,14 +133,14 @@ resource "kubernetes_deployment" "technitium" {
|
|||
image = "technitium/dns-server:latest"
|
||||
name = "technitium"
|
||||
resources {
|
||||
# limits = {
|
||||
# cpu = "1"
|
||||
# memory = "1Gi"
|
||||
# }
|
||||
# requests = {
|
||||
# cpu = "1"
|
||||
# memory = "1Gi"
|
||||
# }
|
||||
requests = {
|
||||
cpu = "100m"
|
||||
memory = "128Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "500m"
|
||||
memory = "512Mi"
|
||||
}
|
||||
}
|
||||
port {
|
||||
container_port = 5380
|
||||
|
|
@ -162,7 +164,7 @@ resource "kubernetes_deployment" "technitium" {
|
|||
name = "nfs-config"
|
||||
nfs {
|
||||
path = "/mnt/main/technitium"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
volume {
|
||||
|
|
@ -278,7 +280,7 @@ resource "kubernetes_config_map" "grafana_technitium_datasource" {
|
|||
name = "Technitium MySQL"
|
||||
type = "mysql"
|
||||
access = "proxy"
|
||||
url = "mysql.dbaas.svc.cluster.local:3306"
|
||||
url = "${var.mysql_host}:3306"
|
||||
database = "technitium"
|
||||
user = "technitium"
|
||||
uid = "technitium-mysql"
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ resource "helm_release" "traefik" {
|
|||
|
||||
# Enable dashboard API (accessible on port 8080 internally)
|
||||
api = {
|
||||
insecure = true
|
||||
insecure = false
|
||||
}
|
||||
|
||||
# Entrypoints
|
||||
|
|
@ -174,7 +174,6 @@ resource "helm_release" "traefik" {
|
|||
}
|
||||
|
||||
additionalArguments = [
|
||||
"--api.insecure=true",
|
||||
"--global.checknewversion=false",
|
||||
"--global.sendanonymoususage=false",
|
||||
# Skip TLS verification for self-signed backend certs (proxmox, idrac, etc.)
|
||||
|
|
@ -184,8 +183,10 @@ resource "helm_release" "traefik" {
|
|||
"--serversTransport.forwardingTimeouts.responseHeaderTimeout=0s",
|
||||
"--serversTransport.forwardingTimeouts.idleConnTimeout=90s",
|
||||
# Use forwarded headers from trusted proxies
|
||||
"--entryPoints.websecure.forwardedHeaders.insecure=true",
|
||||
"--entryPoints.web.forwardedHeaders.insecure=true",
|
||||
"--entryPoints.websecure.forwardedHeaders.insecure=false",
|
||||
"--entryPoints.web.forwardedHeaders.insecure=false",
|
||||
"--entryPoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,10.0.0.0/8,192.168.0.0/16",
|
||||
"--entryPoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,10.0.0.0/8,192.168.0.0/16",
|
||||
]
|
||||
|
||||
resources = {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ resource "kubernetes_manifest" "middleware_rate_limit" {
|
|||
}
|
||||
spec = {
|
||||
rateLimit = {
|
||||
average = 5
|
||||
burst = 250
|
||||
average = 10
|
||||
burst = 50
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -113,6 +113,31 @@ resource "kubernetes_manifest" "middleware_csp_headers" {
|
|||
depends_on = [helm_release.traefik]
|
||||
}
|
||||
|
||||
# Security headers middleware (HSTS, X-Frame-Options, etc.)
|
||||
resource "kubernetes_manifest" "middleware_security_headers" {
|
||||
manifest = {
|
||||
apiVersion = "traefik.io/v1alpha1"
|
||||
kind = "Middleware"
|
||||
metadata = {
|
||||
name = "security-headers"
|
||||
namespace = kubernetes_namespace.traefik.metadata[0].name
|
||||
}
|
||||
spec = {
|
||||
headers = {
|
||||
stsSeconds = 31536000
|
||||
stsIncludeSubdomains = true
|
||||
frameDeny = true
|
||||
contentTypeNosniff = true
|
||||
browserXssFilter = true
|
||||
referrerPolicy = "strict-origin-when-cross-origin"
|
||||
permissionsPolicy = "camera=(), microphone=(), geolocation=()"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [helm_release.traefik]
|
||||
}
|
||||
|
||||
# CrowdSec bouncer plugin middleware
|
||||
resource "kubernetes_manifest" "middleware_crowdsec" {
|
||||
manifest = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
variable "tls_secret_name" {}
|
||||
variable "tier" { type = string }
|
||||
variable "nfs_server" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "uptime-kuma" {
|
||||
metadata {
|
||||
|
|
@ -56,6 +57,17 @@ resource "kubernetes_deployment" "uptime-kuma" {
|
|||
image = "louislam/uptime-kuma:2"
|
||||
name = "uptime-kuma"
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "50m"
|
||||
memory = "64Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "200m"
|
||||
memory = "256Mi"
|
||||
}
|
||||
}
|
||||
|
||||
port {
|
||||
container_port = 3001
|
||||
}
|
||||
|
|
@ -67,7 +79,7 @@ resource "kubernetes_deployment" "uptime-kuma" {
|
|||
volume {
|
||||
name = "data"
|
||||
nfs {
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
path = "/mnt/main/uptime-kuma"
|
||||
}
|
||||
}
|
||||
|
|
@ -160,7 +172,7 @@ module "ingress" {
|
|||
# volume {
|
||||
# name = "data"
|
||||
# nfs {
|
||||
# server = "10.0.10.15"
|
||||
# server = var.nfs_server
|
||||
# path = "/mnt/main/uptime-kuma"
|
||||
# }
|
||||
# }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
variable "tls_secret_name" {}
|
||||
variable "tier" { type = string }
|
||||
variable "smtp_password" {}
|
||||
variable "nfs_server" { type = string }
|
||||
variable "mail_host" { type = string }
|
||||
|
||||
resource "kubernetes_namespace" "vaultwarden" {
|
||||
metadata {
|
||||
|
|
@ -51,6 +53,18 @@ resource "kubernetes_deployment" "vaultwarden" {
|
|||
container {
|
||||
image = "vaultwarden/server:1.35.2"
|
||||
name = "vaultwarden"
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "50m"
|
||||
memory = "64Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "200m"
|
||||
memory = "256Mi"
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "DOMAIN"
|
||||
value = "https://vaultwarden.viktorbarzin.me"
|
||||
|
|
@ -61,7 +75,7 @@ resource "kubernetes_deployment" "vaultwarden" {
|
|||
# }
|
||||
env {
|
||||
name = "SMTP_HOST"
|
||||
value = "mail.viktorbarzin.me"
|
||||
value = var.mail_host
|
||||
}
|
||||
env {
|
||||
name = "SMTP_FROM"
|
||||
|
|
@ -96,7 +110,7 @@ resource "kubernetes_deployment" "vaultwarden" {
|
|||
name = "data"
|
||||
nfs {
|
||||
path = "/mnt/main/vaultwarden"
|
||||
server = "10.0.10.15"
|
||||
server = var.nfs_server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,109 +186,36 @@ resource "kubernetes_service" "xray-reality" {
|
|||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_ingress_v1" "ingress" {
|
||||
metadata {
|
||||
namespace = kubernetes_namespace.xray.metadata[0].name
|
||||
name = "xray"
|
||||
annotations = {
|
||||
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd"
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
||||
}
|
||||
}
|
||||
module "ingress_ws" {
|
||||
source = "../../../../modules/kubernetes/ingress_factory"
|
||||
namespace = kubernetes_namespace.xray.metadata[0].name
|
||||
name = "xray-ws"
|
||||
service_name = "xray"
|
||||
host = "xray-ws"
|
||||
port = 8443
|
||||
tls_secret_name = var.tls_secret_name
|
||||
}
|
||||
|
||||
spec {
|
||||
ingress_class_name = "traefik"
|
||||
tls {
|
||||
hosts = ["xray-ws.viktorbarzin.me"]
|
||||
secret_name = var.tls_secret_name
|
||||
}
|
||||
rule {
|
||||
host = "xray-ws.viktorbarzin.me"
|
||||
http {
|
||||
path {
|
||||
backend {
|
||||
service {
|
||||
name = "xray"
|
||||
port {
|
||||
number = 8443
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
module "ingress_grpc" {
|
||||
source = "../../../../modules/kubernetes/ingress_factory"
|
||||
namespace = kubernetes_namespace.xray.metadata[0].name
|
||||
name = "xray-grpc"
|
||||
service_name = "xray"
|
||||
host = "xray-grpc"
|
||||
port = 9443
|
||||
tls_secret_name = var.tls_secret_name
|
||||
ingress_path = ["/grpc-vpn"]
|
||||
extra_annotations = {
|
||||
"traefik.ingress.kubernetes.io/service.serversscheme" = "h2c"
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_ingress_v1" "ingress-grpc" {
|
||||
metadata {
|
||||
namespace = kubernetes_namespace.xray.metadata[0].name
|
||||
name = "xray-grpc"
|
||||
annotations = {
|
||||
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd"
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
||||
"traefik.ingress.kubernetes.io/service.serversscheme" = "h2c"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
ingress_class_name = "traefik"
|
||||
tls {
|
||||
hosts = ["xray-grpc.viktorbarzin.me"]
|
||||
secret_name = var.tls_secret_name
|
||||
}
|
||||
rule {
|
||||
host = "xray-grpc.viktorbarzin.me"
|
||||
http {
|
||||
path {
|
||||
path = "/grpc-vpn"
|
||||
path_type = "Prefix"
|
||||
backend {
|
||||
service {
|
||||
name = "xray"
|
||||
port {
|
||||
number = 9443
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_ingress_v1" "ingress-vless" {
|
||||
metadata {
|
||||
namespace = kubernetes_namespace.xray.metadata[0].name
|
||||
name = "xray-vless"
|
||||
annotations = {
|
||||
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd"
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
ingress_class_name = "traefik"
|
||||
tls {
|
||||
hosts = ["xray-vless.viktorbarzin.me"]
|
||||
secret_name = var.tls_secret_name
|
||||
}
|
||||
rule {
|
||||
host = "xray-vless.viktorbarzin.me"
|
||||
http {
|
||||
path {
|
||||
backend {
|
||||
service {
|
||||
name = "xray"
|
||||
port {
|
||||
number = 6443
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
module "ingress_vless" {
|
||||
source = "../../../../modules/kubernetes/ingress_factory"
|
||||
namespace = kubernetes_namespace.xray.metadata[0].name
|
||||
name = "xray-vless"
|
||||
service_name = "xray"
|
||||
host = "xray-vless"
|
||||
port = 6443
|
||||
tls_secret_name = var.tls_secret_name
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue