Compare commits
7 commits
c76e84cd77
...
3bda3ab956
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bda3ab956 | ||
|
|
6928fd29bb | ||
|
|
0b58abc7b7 | ||
|
|
fe05b1442c | ||
|
|
82ae4b411a | ||
|
|
6e4cfb4c3a | ||
|
|
d8eab79593 |
14 changed files with 446 additions and 20 deletions
7
main.tf
7
main.tf
|
|
@ -136,6 +136,9 @@ variable "aiostreams_database_connection_string" { type = string }
|
|||
variable "actualbudget_credentials" { type = map(any) }
|
||||
variable "speedtest_db_password" { type = string }
|
||||
variable "freedify_credentials" { type = map(any) }
|
||||
variable "mcaptcha_postgresql_password" { type = string }
|
||||
variable "mcaptcha_cookie_secret" { type = string }
|
||||
variable "mcaptcha_captcha_salt" { type = string }
|
||||
|
||||
provider "kubernetes" {
|
||||
config_path = var.prod ? "" : "~/.kube/config"
|
||||
|
|
@ -563,6 +566,10 @@ module "kubernetes_cluster" {
|
|||
|
||||
speedtest_db_password = var.speedtest_db_password
|
||||
freedify_credentials = var.freedify_credentials
|
||||
|
||||
mcaptcha_postgresql_password = var.mcaptcha_postgresql_password
|
||||
mcaptcha_cookie_secret = var.mcaptcha_cookie_secret
|
||||
mcaptcha_captcha_salt = var.mcaptcha_captcha_salt
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,28 @@ resource "kubernetes_config_map" "crowdsec_custom_scenarios" {
|
|||
}
|
||||
}
|
||||
|
||||
# Whitelist for trusted IPs that should never be blocked
|
||||
resource "kubernetes_config_map" "crowdsec_whitelist" {
|
||||
metadata {
|
||||
name = "crowdsec-whitelist"
|
||||
namespace = kubernetes_namespace.crowdsec.metadata[0].name
|
||||
labels = {
|
||||
"app.kubernetes.io/name" = "crowdsec"
|
||||
}
|
||||
}
|
||||
|
||||
data = {
|
||||
"whitelist.yaml" = <<-YAML
|
||||
name: crowdsecurity/whitelist-trusted-ips
|
||||
description: "Whitelist for trusted IPs that should never be blocked"
|
||||
whitelist:
|
||||
reason: "Trusted IP - never block"
|
||||
ip:
|
||||
- "176.12.22.76"
|
||||
YAML
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resource "helm_release" "crowdsec" {
|
||||
namespace = kubernetes_namespace.crowdsec.metadata[0].name
|
||||
|
|
|
|||
|
|
@ -31,10 +31,17 @@ agent:
|
|||
mountPath: /etc/crowdsec/scenarios/http-429-abuse.yaml
|
||||
subPath: "http-429-abuse.yaml"
|
||||
readonly: true
|
||||
- name: whitelist
|
||||
mountPath: /etc/crowdsec/parsers/s02-enrich/whitelist.yaml
|
||||
subPath: "whitelist.yaml"
|
||||
readonly: true
|
||||
extraVolumes:
|
||||
- name: custom-scenarios
|
||||
configMap:
|
||||
name: crowdsec-custom-scenarios
|
||||
- name: whitelist
|
||||
configMap:
|
||||
name: crowdsec-whitelist
|
||||
lapi:
|
||||
replicas: 3
|
||||
extraSecrets:
|
||||
|
|
@ -117,6 +124,34 @@ lapi:
|
|||
type: RollingUpdate
|
||||
|
||||
config:
|
||||
# Custom profiles: captcha for rate limiting, ban for attacks
|
||||
profiles.yaml: |
|
||||
# Captcha for rate limiting and 403 abuse (user can unblock themselves)
|
||||
name: captcha_remediation
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() in ["crowdsecurity/http-429-abuse", "crowdsecurity/http-403-abuse", "crowdsecurity/http-crawl-non_statics", "crowdsecurity/http-sensitive-files"]
|
||||
decisions:
|
||||
- type: captcha
|
||||
duration: 4h
|
||||
on_success: break
|
||||
---
|
||||
# Default: Ban for serious attacks (CVE exploits, scanners, brute force)
|
||||
name: default_ip_remediation
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Ip"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 4h
|
||||
on_success: break
|
||||
---
|
||||
name: default_range_remediation
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Range"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 4h
|
||||
on_success: break
|
||||
|
||||
config.yaml.local: |
|
||||
db_config:
|
||||
type: mysql
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
variable namespace {}
|
||||
variable password {}
|
||||
variable dockerhub_creds_secret_name {
|
||||
variable "namespace" {}
|
||||
variable "password" {}
|
||||
variable "dockerhub_creds_secret_name" {
|
||||
default = "dockerhub-creds"
|
||||
}
|
||||
variable username {
|
||||
variable "username" {
|
||||
default = "viktorbarzin"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ resource "kubernetes_deployment" "immich-machine-learning" {
|
|||
}
|
||||
env {
|
||||
name = "MACHINE_LEARNING_MODEL_TTL"
|
||||
value = 0
|
||||
value = "0"
|
||||
}
|
||||
env {
|
||||
name = "TRANSFORMERS_CACHE"
|
||||
|
|
@ -388,10 +388,24 @@ resource "kubernetes_deployment" "immich-machine-learning" {
|
|||
name = "MPLCONFIGDIR"
|
||||
value = "/cache/matplotlib-config"
|
||||
}
|
||||
# Preload CLIP models (for smart search)
|
||||
env {
|
||||
name = "MACHINE_LEARNING_PRELOAD__CLIP"
|
||||
name = "MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"
|
||||
value = "ViT-B-16-SigLIP2__webli"
|
||||
}
|
||||
env {
|
||||
name = "MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"
|
||||
value = "ViT-B-16-SigLIP2__webli"
|
||||
}
|
||||
# Preload facial recognition models
|
||||
env {
|
||||
name = "MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"
|
||||
value = "buffalo_l"
|
||||
}
|
||||
env {
|
||||
name = "MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"
|
||||
value = "buffalo_l"
|
||||
}
|
||||
|
||||
volume_mount {
|
||||
name = "cache"
|
||||
|
|
|
|||
|
|
@ -115,6 +115,9 @@ variable "aiostreams_database_connection_string" { type = string }
|
|||
variable "actualbudget_credentials" { type = map(any) }
|
||||
variable "speedtest_db_password" { type = string }
|
||||
variable "freedify_credentials" { type = map(any) }
|
||||
variable "mcaptcha_postgresql_password" { type = string }
|
||||
variable "mcaptcha_cookie_secret" { type = string }
|
||||
variable "mcaptcha_captcha_salt" { type = string }
|
||||
|
||||
|
||||
variable "defcon_level" {
|
||||
|
|
@ -140,7 +143,7 @@ locals {
|
|||
"url", "excalidraw", "travel_blog", "dashy", "send", "ytdlp", "wealthfolio", "rybbit", "stirling-pdf",
|
||||
"networking-toolbox", "navidrome", "freshrss", "forgejo", "tor-proxy", "real-estate-crawler", "n8n",
|
||||
"changedetection", "linkwarden", "matrix", "homepage", "meshcentral", "diun", "cyberchef", "ntfy", "ollama",
|
||||
"servarr", "jsoncrack", "paperless-ngx", "frigate", "audiobookshelf", "tandoor", "ebook2audiobook", "netbox", "speedtest", "resume", "freedify"
|
||||
"servarr", "jsoncrack", "paperless-ngx", "frigate", "audiobookshelf", "tandoor", "ebook2audiobook", "netbox", "speedtest", "resume", "freedify", "mcaptcha"
|
||||
],
|
||||
}
|
||||
active_modules = distinct(flatten([
|
||||
|
|
@ -332,6 +335,18 @@ module "privatebin" {
|
|||
depends_on = [null_resource.core_services]
|
||||
}
|
||||
|
||||
# module "mcaptcha" {
|
||||
# source = "./mcaptcha"
|
||||
# for_each = contains(local.active_modules, "mcaptcha") ? { mcaptcha = true } : {}
|
||||
# tls_secret_name = var.tls_secret_name
|
||||
# tier = local.tiers.edge
|
||||
# postgresql_password = var.mcaptcha_postgresql_password
|
||||
# cookie_secret = var.mcaptcha_cookie_secret
|
||||
# captcha_salt = var.mcaptcha_captcha_salt
|
||||
|
||||
# depends_on = [null_resource.core_services]
|
||||
# }
|
||||
|
||||
# module "vault" {
|
||||
# source = "./vault"
|
||||
# tier = local.tiers.edge
|
||||
|
|
|
|||
309
modules/kubernetes/mcaptcha/main.tf
Normal file
309
modules/kubernetes/mcaptcha/main.tf
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
variable "tls_secret_name" {}
|
||||
variable "tier" { type = string }
|
||||
variable "postgresql_password" {}
|
||||
variable "cookie_secret" {}
|
||||
variable "captcha_salt" {}
|
||||
|
||||
locals {
|
||||
domain = "mcaptcha.viktorbarzin.me"
|
||||
port = 7000
|
||||
}
|
||||
|
||||
resource "kubernetes_namespace" "mcaptcha" {
|
||||
metadata {
|
||||
name = "mcaptcha"
|
||||
labels = {
|
||||
"istio-injection" : "disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "tls_secret" {
|
||||
source = "../setup_tls_secret"
|
||||
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
|
||||
tls_secret_name = var.tls_secret_name
|
||||
}
|
||||
|
||||
# mCaptcha requires a special Redis with the mcaptcha/cache module loaded
|
||||
resource "kubernetes_deployment" "mcaptcha_redis" {
|
||||
metadata {
|
||||
name = "mcaptcha-redis"
|
||||
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
|
||||
labels = {
|
||||
app = "mcaptcha-redis"
|
||||
tier = var.tier
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
replicas = 1
|
||||
selector {
|
||||
match_labels = {
|
||||
app = "mcaptcha-redis"
|
||||
}
|
||||
}
|
||||
|
||||
strategy {
|
||||
type = "Recreate"
|
||||
}
|
||||
|
||||
template {
|
||||
metadata {
|
||||
labels = {
|
||||
app = "mcaptcha-redis"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
container {
|
||||
image = "mcaptcha/cache:latest"
|
||||
name = "redis"
|
||||
|
||||
port {
|
||||
container_port = 6379
|
||||
}
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
memory = "64Mi"
|
||||
cpu = "25m"
|
||||
}
|
||||
limits = {
|
||||
memory = "128Mi"
|
||||
cpu = "200m"
|
||||
}
|
||||
}
|
||||
|
||||
liveness_probe {
|
||||
tcp_socket {
|
||||
port = 6379
|
||||
}
|
||||
initial_delay_seconds = 10
|
||||
period_seconds = 10
|
||||
}
|
||||
|
||||
readiness_probe {
|
||||
tcp_socket {
|
||||
port = 6379
|
||||
}
|
||||
initial_delay_seconds = 5
|
||||
period_seconds = 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_service" "mcaptcha_redis" {
|
||||
metadata {
|
||||
name = "mcaptcha-redis"
|
||||
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
|
||||
labels = {
|
||||
app = "mcaptcha-redis"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
selector = {
|
||||
app = "mcaptcha-redis"
|
||||
}
|
||||
port {
|
||||
name = "redis"
|
||||
port = 6379
|
||||
target_port = 6379
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_deployment" "mcaptcha" {
|
||||
metadata {
|
||||
name = "mcaptcha"
|
||||
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
|
||||
labels = {
|
||||
app = "mcaptcha"
|
||||
tier = var.tier
|
||||
}
|
||||
annotations = {
|
||||
"reloader.stakater.com/search" = "true"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
replicas = 1
|
||||
selector {
|
||||
match_labels = {
|
||||
app = "mcaptcha"
|
||||
}
|
||||
}
|
||||
|
||||
strategy {
|
||||
type = "Recreate"
|
||||
}
|
||||
|
||||
template {
|
||||
metadata {
|
||||
labels = {
|
||||
app = "mcaptcha"
|
||||
}
|
||||
annotations = {
|
||||
"diun.enable" = "true"
|
||||
"diun.include_tags" = "^\\d+(?:\\.\\d+)?(?:\\.\\d+)?$"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
container {
|
||||
image = "mcaptcha/mcaptcha:latest"
|
||||
name = "mcaptcha"
|
||||
|
||||
port {
|
||||
container_port = local.port
|
||||
}
|
||||
|
||||
# Required configuration
|
||||
env {
|
||||
name = "MCAPTCHA_server_DOMAIN"
|
||||
value = local.domain
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_server_COOKIE_SECRET"
|
||||
value = var.cookie_secret
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_captcha_SALT"
|
||||
value = var.captcha_salt
|
||||
}
|
||||
|
||||
# Server configuration
|
||||
env {
|
||||
name = "PORT"
|
||||
value = tostring(local.port)
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_server_IP"
|
||||
value = "0.0.0.0"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_server_PROXY_HAS_TLS"
|
||||
value = "true"
|
||||
}
|
||||
|
||||
# Database configuration (PostgreSQL)
|
||||
env {
|
||||
name = "DATABASE_URL"
|
||||
value = "postgres://mcaptcha:${var.postgresql_password}@postgresql.dbaas.svc.cluster.local:5432/mcaptcha"
|
||||
}
|
||||
|
||||
# Redis configuration (using mcaptcha/cache module)
|
||||
env {
|
||||
name = "MCAPTCHA_redis_URL"
|
||||
value = "redis://mcaptcha-redis.mcaptcha.svc.cluster.local:6379"
|
||||
}
|
||||
|
||||
# Feature flags
|
||||
env {
|
||||
name = "MCAPTCHA_allow_registration"
|
||||
# value = "true"
|
||||
value = "false"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_allow_demo"
|
||||
value = "false"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_commercial"
|
||||
value = "false"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_captcha_ENABLE_STATS"
|
||||
value = "true"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_captcha_GC"
|
||||
value = "30"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "MCAPTCHA_debug"
|
||||
value = "false"
|
||||
}
|
||||
env {
|
||||
name = "RUST_BACKTRACE"
|
||||
value = "1"
|
||||
}
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
memory = "64Mi"
|
||||
cpu = "50m"
|
||||
}
|
||||
limits = {
|
||||
memory = "256Mi"
|
||||
cpu = "500m"
|
||||
}
|
||||
}
|
||||
|
||||
# Health checks
|
||||
liveness_probe {
|
||||
http_get {
|
||||
path = "/"
|
||||
port = local.port
|
||||
}
|
||||
initial_delay_seconds = 30
|
||||
period_seconds = 10
|
||||
timeout_seconds = 5
|
||||
failure_threshold = 3
|
||||
}
|
||||
|
||||
readiness_probe {
|
||||
http_get {
|
||||
path = "/"
|
||||
port = local.port
|
||||
}
|
||||
initial_delay_seconds = 10
|
||||
period_seconds = 5
|
||||
timeout_seconds = 3
|
||||
failure_threshold = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_service" "mcaptcha" {
|
||||
metadata {
|
||||
name = "mcaptcha"
|
||||
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
|
||||
labels = {
|
||||
"app" = "mcaptcha"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
selector = {
|
||||
app = "mcaptcha"
|
||||
}
|
||||
port {
|
||||
name = "http"
|
||||
port = 80
|
||||
target_port = local.port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "ingress" {
|
||||
source = "../ingress_factory"
|
||||
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
|
||||
name = "mcaptcha"
|
||||
tls_secret_name = var.tls_secret_name
|
||||
}
|
||||
|
|
@ -21,6 +21,14 @@ resource "kubernetes_config_map" "redfish-config" {
|
|||
password: calvin
|
||||
metrics:
|
||||
all: true
|
||||
# system: true
|
||||
# sensors: true
|
||||
# power: true
|
||||
# sel: false # Disable SEL - often slow
|
||||
# storage: true # Disable storage - slowest endpoint
|
||||
# memory: true
|
||||
# network: false # Disable network adapters
|
||||
# firmware: false # Don't need this frequently
|
||||
EOF
|
||||
}
|
||||
}
|
||||
|
|
@ -83,11 +91,11 @@ resource "kubernetes_service" "idrac-redfish-exporter" {
|
|||
labels = {
|
||||
"app" = "idrac-redfish-exporter"
|
||||
}
|
||||
annotations = {
|
||||
"prometheus.io/scrape" = "true"
|
||||
"prometheus.io/path" = "/metrics"
|
||||
"prometheus.io/port" = "9090"
|
||||
}
|
||||
# annotations = {
|
||||
# "prometheus.io/scrape" = "true"
|
||||
# "prometheus.io/path" = "/metrics"
|
||||
# "prometheus.io/port" = "9090"
|
||||
# }
|
||||
}
|
||||
|
||||
spec {
|
||||
|
|
|
|||
|
|
@ -474,6 +474,8 @@ extraScrapeConfigs: |
|
|||
- "crowdsec-service.crowdsec.svc.cluster.local:6060"
|
||||
metrics_path: '/metrics'
|
||||
- job_name: 'snmp-idrac'
|
||||
scrape_interval: 1m
|
||||
scrape_timeout: 45s
|
||||
static_configs:
|
||||
- targets:
|
||||
- "idrac.viktorbarzin.lan:161"
|
||||
|
|
@ -492,7 +494,7 @@ extraScrapeConfigs: |
|
|||
regex: '(.*)'
|
||||
replacement: 'r730_idrac_$${1}'
|
||||
- job_name: 'redfish-idrac'
|
||||
scrape_interval: 1m
|
||||
scrape_interval: 3m
|
||||
scrape_timeout: 45s
|
||||
metrics_path: /metrics
|
||||
static_configs:
|
||||
|
|
|
|||
|
|
@ -82,11 +82,11 @@ resource "kubernetes_service" "snmp-exporter" {
|
|||
labels = {
|
||||
"app" = "snmp-exporter"
|
||||
}
|
||||
annotations = {
|
||||
"prometheus.io/scrape" = "true"
|
||||
"prometheus.io/path" = "/snmp?auth=Public0&target=tcp%3A%2F%2F192.%3A161"
|
||||
"prometheus.io/port" = "9116"
|
||||
}
|
||||
# annotations = {
|
||||
# "prometheus.io/scrape" = "true"
|
||||
# "prometheus.io/path" = "/snmp?auth=Public0&target=tcp%3A%2F%2F192.%3A161"
|
||||
# "prometheus.io/port" = "9116"
|
||||
# }
|
||||
}
|
||||
|
||||
spec {
|
||||
|
|
|
|||
|
|
@ -539,7 +539,7 @@ resource "kubernetes_deployment" "ingress_nginx_controller" {
|
|||
}
|
||||
env {
|
||||
name = "CAPTCHA_PROVIDER"
|
||||
value = "recaptcha"
|
||||
value = "hcaptcha"
|
||||
}
|
||||
env {
|
||||
name = "BOUNCING_ON_TYPE"
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ resource "kubernetes_service" "ollama" {
|
|||
}
|
||||
}
|
||||
|
||||
# Allow ollama to be connected to from external apps
|
||||
# Allow ollama to be connected to from external apps (internal LAN only)
|
||||
module "ollama-ingress" {
|
||||
source = "../ingress_factory"
|
||||
namespace = kubernetes_namespace.ollama.metadata[0].name
|
||||
|
|
@ -158,6 +158,20 @@ module "ollama-ingress" {
|
|||
port = 11434
|
||||
}
|
||||
|
||||
# Ollama API ingress for Claude Code access (restricted to LAN/VPN)
|
||||
module "ollama-api-ingress" {
|
||||
source = "../ingress_factory"
|
||||
namespace = kubernetes_namespace.ollama.metadata[0].name
|
||||
name = "ollama-api"
|
||||
service_name = "ollama"
|
||||
root_domain = "viktorbarzin.lan"
|
||||
tls_secret_name = var.tls_secret_name
|
||||
allow_local_access_only = true # Restricts to 10.0.0.0/8, 192.168.1.0/24
|
||||
ssl_redirect = false
|
||||
port = 11434
|
||||
proxy_timeout = 300 # Longer timeout for model inference
|
||||
}
|
||||
|
||||
# Web UI
|
||||
resource "kubernetes_deployment" "ollama-ui" {
|
||||
metadata {
|
||||
|
|
|
|||
Binary file not shown.
BIN
terraform.tfvars
BIN
terraform.tfvars
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue