# CrowdSec Security Engine Deployment # Monitors and protects against malicious traffic on NGINX Ingress resource "kubernetes_namespace" "crowdsec" { metadata { name = "crowdsec" } } # PersistentVolume for LAPI config on host path resource "kubernetes_persistent_volume" "crowdsec_lapi_config" { metadata { name = "crowdsec-lapi-config-pv" } spec { capacity = { storage = "1Gi" } access_modes = ["ReadWriteOnce"] storage_class_name = "local-path" persistent_volume_source { host_path { path = "/mnt/k8s/crowdsec/lapi-config" type = "DirectoryOrCreate" } } node_affinity { required { node_selector_term { match_expressions { key = "kubernetes.io/hostname" operator = "In" values = ["server-3"] } } } } } } # PersistentVolume for LAPI data on host path resource "kubernetes_persistent_volume" "crowdsec_lapi_data" { metadata { name = "crowdsec-lapi-data-pv" } spec { capacity = { storage = "2Gi" } access_modes = ["ReadWriteOnce"] storage_class_name = "local-path" persistent_volume_source { host_path { path = "/mnt/k8s/crowdsec/lapi-data" type = "DirectoryOrCreate" } } node_affinity { required { node_selector_term { match_expressions { key = "kubernetes.io/hostname" operator = "In" values = ["server-3"] } } } } } } # ConfigMap for GitHub IP whitelist — mounted into the LAPI pod as a parser whitelist file. # This survives CrowdSec upgrades/restarts because it comes from a ConfigMap, not the ephemeral container FS. resource "kubernetes_config_map" "crowdsec_github_whitelist" { metadata { name = "crowdsec-github-whitelist" namespace = kubernetes_namespace.crowdsec.metadata[0].name } data = { "github-whitelist.yaml" = <<-EOT name: whitelist-github-ips description: "Whitelist GitHub Actions/Services IPs (140.82.115.0/24)" whitelist: reason: "GitHub Actions and Services" cidr: - "140.82.115.0/24" EOT } } resource "helm_release" "crowdsec" { name = "crowdsec" repository = "https://crowdsecurity.github.io/helm-charts" chart = "crowdsec" version = "0.24.0" namespace = kubernetes_namespace.crowdsec.metadata[0].name wait = true timeout = 600 values = [ yamlencode({ container_runtime = "containerd" image = { repository = "crowdsecurity/crowdsec" tag = "v1.7.8" pullPolicy = "IfNotPresent" } lapi = { enabled = true replicas = 1 resources = { limits = { memory = "512Mi" cpu = "500m" } requests = { memory = "256Mi" cpu = "100m" } } persistentVolume = { config = { enabled = true accessModes = ["ReadWriteOnce"] storageClassName = "local-path" size = "1Gi" } data = { enabled = true accessModes = ["ReadWriteOnce"] storageClassName = "local-path" size = "2Gi" } } metrics = { enabled = true } # Mount the GitHub IP whitelist ConfigMap into the LAPI parser directory extraVolumes = [ { name = "github-whitelist" configMap = { name = kubernetes_config_map.crowdsec_github_whitelist.metadata[0].name } } ] extraVolumeMounts = [ { name = "github-whitelist" mountPath = "/etc/crowdsec/parsers/s02-enrich/github-whitelist.yaml" subPath = "github-whitelist.yaml" } ] env = [ { name = "ENROLL_KEY" value = "cmgcsgn14000e02l1xmme5qnm" }, { name = "ENROLL_INSTANCE_NAME" value = "junv-home-k3s-cluster" }, { name = "ENROLL_TAGS" value = "junv k3s homelab nginx" } ] } agent = { enabled = true isDeployment = false resources = { limits = { memory = "512Mi" cpu = "500m" } requests = { memory = "256Mi" cpu = "100m" } } hostVarLog = true metrics = { enabled = true } # Monitor NGINX Ingress Controller logs and SSH logs acquisition = [ { namespace = "ingress-nginx" podName = "ingress-nginx-controller-*" program = "nginx" poll_without_inotify = true # Container logs are symlinks, need polling } ] # Additional acquisition for host SSH logs and firewall logs additionalAcquisition = [ { source = "file" filenames = ["/var/log/auth.log"] labels = { type = "syslog" } }, { source = "file" filenames = ["/var/log/kern.log", "/var/log/syslog"] labels = { type = "syslog" } } ] # Install NGINX, SSH, and iptables/firewall collections env = [ { name = "COLLECTIONS" value = "crowdsecurity/nginx crowdsecurity/base-http-scenarios crowdsecurity/linux crowdsecurity/sshd crowdsecurity/iptables crowdsecurity/endlessh" } ] persistentVolume = { config = { enabled = false } } } config = { "config.yaml.local" = <<-EOT api: server: auto_registration: enabled: true token: "$${REGISTRATION_TOKEN}" allowed_ranges: - "127.0.0.1/32" - "10.0.0.0/8" - "172.16.0.0/12" - "192.168.0.0/16" log_level: info listen_uri: 0.0.0.0:8080 client: insecure_skip_verify: false common: daemonize: false log_media: stdout log_level: info EOT "profiles.yaml.local" = <<-EOT name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 24h on_success: break EOT } }) ] depends_on = [ kubernetes_namespace.crowdsec, kubernetes_persistent_volume.crowdsec_lapi_config, kubernetes_persistent_volume.crowdsec_lapi_data, kubernetes_config_map.crowdsec_github_whitelist, ] } # Wait for CrowdSec to be ready and create bouncer resource "null_resource" "crowdsec_bouncer_setup" { depends_on = [helm_release.crowdsec] provisioner "local-exec" { command = <<-EOT echo "Waiting for CrowdSec LAPI to be ready..." kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=crowdsec,app.kubernetes.io/component=lapi -n crowdsec --timeout=300s echo "Creating NGINX bouncer..." BOUNCER_KEY=$(kubectl exec -n crowdsec deployment/crowdsec-lapi -- cscli bouncers add nginx-ingress-bouncer -o raw 2>/dev/null || echo "") if [ -z "$BOUNCER_KEY" ]; then echo "Bouncer may already exist, retrieving existing key..." kubectl exec -n crowdsec deployment/crowdsec-lapi -- cscli bouncers list else echo "Bouncer created successfully!" echo "Creating Kubernetes secret with bouncer key..." kubectl create secret generic crowdsec-bouncer-tls -n ingress-nginx \ --from-literal=crowdsec-api=http://crowdsec-service.crowdsec.svc.cluster.local:8080 \ --from-literal=crowdsec-key="$BOUNCER_KEY" \ --dry-run=client -o yaml | kubectl apply -f - echo "Bouncer API Key: $BOUNCER_KEY" fi echo "" echo "CrowdSec setup complete!" echo "To view status: kubectl exec -n crowdsec deployment/crowdsec-lapi -- cscli metrics" EOT } triggers = { crowdsec_version = helm_release.crowdsec.version } } # DISABLED: iptables logging was causing connectivity issues with kubectl # The rules were blocking legitimate Kubernetes traffic # CrowdSec will still monitor logs without iptables integration # resource "ssh_resource" "iptables_logging" { # depends_on = [helm_release.crowdsec] # # host = local.ssh_host # user = local.ssh_user # port = local.ssh_port # private_key = local.ssh_private_key # timeout = "5m" # # when = "create" # # commands = [ # "echo 'Setting up iptables logging for CrowdSec...'", # # # Create chain for suspicious activity logging # "sudo iptables -N CROWDSEC_SUSPICIOUS 2>/dev/null || true", # "sudo iptables -F CROWDSEC_SUSPICIOUS 2>/dev/null || true", # # # IMPORTANT: Exclude local network (192.168.1.0/24) from ALL port scan detection # "sudo iptables -I INPUT 1 -s 192.168.1.0/24 -j ACCEPT || true", # # # Track connection attempts for port scan detection (only for external traffic) # "sudo iptables -A INPUT -p tcp -m state --state NEW -m recent --set --name portscan || true", # # # If more than 10 connection attempts in 60 seconds from external IPs, log it as suspicious # "sudo iptables -A INPUT -p tcp -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name portscan -j CROWDSEC_SUSPICIOUS || true", # # # Log suspicious activity # "sudo iptables -A CROWDSEC_SUSPICIOUS -j LOG --log-prefix 'iptables_SCAN: ' --log-level 4 --log-tcp-options --log-ip-options || true", # # # Drop the suspicious packets # "sudo iptables -A CROWDSEC_SUSPICIOUS -j DROP || true", # # # Log invalid packets from external sources only (often used in attacks) # "sudo iptables -A INPUT -m state --state INVALID -j LOG --log-prefix 'iptables_INVALID: ' --log-level 4 || true", # "sudo iptables -A INPUT -m state --state INVALID -j DROP || true", # # # Install iptables-persistent to save rules # "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent 2>/dev/null || true", # # # Save the rules # "sudo mkdir -p /etc/iptables", # "sudo iptables-save | sudo tee /etc/iptables/rules.v4 > /dev/null", # # "echo '✅ iptables logging configured!'", # "echo 'Port scans and suspicious activity will now be logged and detected by CrowdSec'" # ] # } # Remove any existing CrowdSec ban decisions for GitHub IPs (140.82.115.0/24) # Re-runs whenever the whitelist CIDR changes or after a helm upgrade. resource "null_resource" "crowdsec_github_unban" { depends_on = [null_resource.crowdsec_bouncer_setup] provisioner "local-exec" { command = <<-EOT echo "Removing any existing bans for GitHub IPs (140.82.115.0/24)..." kubectl exec -n crowdsec deployment/crowdsec-lapi -- \ cscli decisions delete --range 140.82.115.0/24 || true echo "Done. GitHub IPs are cleared from the ban list." EOT } triggers = { github_cidr = "140.82.115.0/24" crowdsec_version = helm_release.crowdsec.version } } output "crowdsec_info" { value = <<-EOT CrowdSec deployed successfully! Monitoring: - ✅ NGINX Ingress (ingress-nginx namespace) - ✅ SSH attempts (port 22422) - ✅ Port scans (iptables logging enabled) - ✅ System logs Commands: - Status: kubectl get pods -n crowdsec - Metrics: kubectl exec -n crowdsec deployment/crowdsec-lapi -- cscli metrics - Decisions: kubectl exec -n crowdsec deployment/crowdsec-lapi -- cscli decisions list - Bouncers: kubectl exec -n crowdsec deployment/crowdsec-lapi -- cscli bouncers list Storage: /mnt/k8s/crowdsec/ on server-3 (192.168.1.2) Web Dashboard: https://app.crowdsec.net Quick view: ./view_crowdsec.sh EOT }