diff --git a/main.tf b/main.tf index a0e11186..1efffc2e 100644 --- a/main.tf +++ b/main.tf @@ -56,6 +56,7 @@ variable "finance_app_gocardless_secret_key" {} variable "finance_app_gocardless_secret_id" {} variable "headscale_config" {} variable "immich_postgresql_password" {} +variable "ingress_honeypotapikey" {} variable "ansible_prefix" { default = "ANSIBLE_VAULT_PASSWORD_FILE=~/.ansible/vault_pass.txt ansible-playbook -i playbook/hosts.yaml playbook/linux.yml -t linux/initial_setup" @@ -280,6 +281,8 @@ module "kubernetes_cluster" { headscale_config = var.headscale_config immich_postgresql_password = var.immich_postgresql_password + + ingress_honeypotapikey = var.ingress_honeypotapikey } diff --git a/modules/kubernetes/main.tf b/modules/kubernetes/main.tf index a03beb4d..e2d7cfa7 100644 --- a/modules/kubernetes/main.tf +++ b/modules/kubernetes/main.tf @@ -43,6 +43,7 @@ variable "finance_app_gocardless_secret_key" {} variable "finance_app_gocardless_secret_id" {} variable "headscale_config" {} variable "immich_postgresql_password" {} +variable "ingress_honeypotapikey" {} resource "null_resource" "core_services" { # List all the core modules that must be provisioned first @@ -341,5 +342,6 @@ module "immich" { } module "nginx-ingress" { - source = "./nginx-ingress" + source = "./nginx-ingress" + honeypotapikey = var.ingress_honeypotapikey } diff --git a/modules/kubernetes/nginx-ingress/main.tf b/modules/kubernetes/nginx-ingress/main.tf index df1bb9ae..9e905b02 100644 --- a/modules/kubernetes/nginx-ingress/main.tf +++ b/modules/kubernetes/nginx-ingress/main.tf @@ -6,6 +6,9 @@ # ingress_class_is_default = false # ingress_class_name = "nginx-test" # } +variable "honeypotapikey" { + default = null +} resource "kubernetes_namespace" "ingress_nginx" { metadata { name = "ingress-nginx" @@ -303,7 +306,22 @@ resource "kubernetes_config_map" "ingress_nginx_controller" { } } data = { - allow-snippet-annotations = "true" + allow-snippet-annotations = true + enable-modsecurity = true + enable-owasp-modsecurity-crs = true + modsecurity-snippet : <<-EOT + SecRuleEngine On + ${var.honeypotapikey != null ? format("%s %s", "SecHttpBlKey", var.honeypotapikey) : ""} + SecAction "id:900500,\ + phase:1,\ + nolog,\ + pass,\ + t:none,\ + setvar:tx.block_search_ip=0,\ + setvar:tx.block_suspicious_ip=1,\ + setvar:tx.block_harvester_ip=1,\ + setvar:tx.block_spammer_ip=1" + EOT } } resource "kubernetes_service" "ingress_nginx_controller" { @@ -377,6 +395,9 @@ resource "kubernetes_deployment" "ingress_nginx_controller" { "app.kubernetes.io/part-of" = "ingress-nginx" "app.kubernetes.io/version" = "1.8.2" } + annotations = { + "reloader.stakater.com/search" = "true" + } } spec { selector { @@ -403,6 +424,12 @@ resource "kubernetes_deployment" "ingress_nginx_controller" { secret_name = "ingress-nginx-admission" } } + # volume { + # name = "modsecurity" + # config_map { + # name = "modsecurity" + # } + # } container { name = "controller" image = "registry.k8s.io/ingress-nginx/controller:v1.8.2@sha256:74834d3d25b336b62cabeb8bf7f1d788706e2cf1cfd64022de4137ade8881ff2" @@ -453,6 +480,13 @@ resource "kubernetes_deployment" "ingress_nginx_controller" { read_only = true mount_path = "/usr/local/certificates/" } + # Not used atm + # volume_mount { + # name = "modsecurity" + # read_only = true + # mount_path = "/etc/nginx/modsecurity" + # # sub_path = "modsecurity.conf" + # } liveness_probe { http_get { path = "/healthz" @@ -672,3 +706,17 @@ resource "kubernetes_ingress_class" "nginx" { # admission_review_versions = ["v1"] # } # } + +resource "kubernetes_config_map" "modsecurity" { + metadata { + name = "modsecurity" + namespace = "ingress-nginx" + annotations = { + "reloader.stakater.com/match" = "true" + } + } + + data = { + "modsecurity.conf" = file("${path.module}/modsecurity.conf") + } +} diff --git a/modules/kubernetes/nginx-ingress/modsecurity.conf b/modules/kubernetes/nginx-ingress/modsecurity.conf new file mode 100644 index 00000000..a9582678 --- /dev/null +++ b/modules/kubernetes/nginx-ingress/modsecurity.conf @@ -0,0 +1,289 @@ +### NOT USED ATM + + +# -- Rule engine initialization ---------------------------------------------- + +# Enable ModSecurity, attaching it to every transaction. Use detection +# only to start with, because that minimises the chances of post-installation +# disruption. +# +SecRuleEngine DetectionOnly + + +# -- Request body handling --------------------------------------------------- + +# Allow ModSecurity to access request bodies. If you don't, ModSecurity +# won't be able to see any POST parameters, which opens a large security +# hole for attackers to exploit. +# +SecRequestBodyAccess On + + +# Enable XML request body parser. +# Initiate XML Processor in case of xml content-type +# +SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \ + "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + +# Enable JSON request body parser. +# Initiate JSON Processor in case of JSON content-type; change accordingly +# if your application does not use 'application/json' +# +SecRule REQUEST_HEADERS:Content-Type "^application/json" \ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + +# Sample rule to enable JSON request body parser for more subtypes. +# Uncomment or adapt this rule if you want to engage the JSON +# Processor for "+json" subtypes +# +#SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \ +# "id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + +# Maximum request body size we will accept for buffering. If you support +# file uploads then the value given on the first line has to be as large +# as the largest file you are willing to accept. The second value refers +# to the size of data, with files excluded. You want to keep that value as +# low as practical. +# +SecRequestBodyLimit 13107200 +SecRequestBodyNoFilesLimit 131072 + +# What to do if the request body size is above our configured limit. +# Keep in mind that this setting will automatically be set to ProcessPartial +# when SecRuleEngine is set to DetectionOnly mode in order to minimize +# disruptions when initially deploying ModSecurity. +# +SecRequestBodyLimitAction Reject + +# Maximum parsing depth allowed for JSON objects. You want to keep this +# value as low as practical. +# +SecRequestBodyJsonDepthLimit 512 + +# Maximum number of args allowed per request. You want to keep this +# value as low as practical. The value should match that in rule 200007. +SecArgumentsLimit 1000 + +# If SecArgumentsLimit has been set, you probably want to reject any +# request body that has only been partly parsed. The value used in this +# rule should match what was used with SecArgumentsLimit +SecRule &ARGS "@ge 1000" \ +"id:'200007', phase:2,t:none,log,deny,status:400,msg:'Failed to fully parse request body due to large argument count',severity:2" + +# Verify that we've correctly processed the request body. +# As a rule of thumb, when failing to process a request body +# you should reject the request (when deployed in blocking mode) +# or log a high-severity alert (when deployed in detection-only mode). +# +SecRule REQBODY_ERROR "!@eq 0" \ +"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + +# By default be strict with what we accept in the multipart/form-data +# request body. If the rule below proves to be too strict for your +# environment consider changing it to detection-only. You are encouraged +# _not_ to remove it altogether. +# +SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ +"id:'200003',phase:2,t:none,log,deny,status:400, \ +msg:'Multipart request body failed strict validation: \ +PE %{REQBODY_PROCESSOR_ERROR}, \ +BQ %{MULTIPART_BOUNDARY_QUOTED}, \ +BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ +DB %{MULTIPART_DATA_BEFORE}, \ +DA %{MULTIPART_DATA_AFTER}, \ +HF %{MULTIPART_HEADER_FOLDING}, \ +LF %{MULTIPART_LF_LINE}, \ +SM %{MULTIPART_MISSING_SEMICOLON}, \ +IQ %{MULTIPART_INVALID_QUOTING}, \ +IP %{MULTIPART_INVALID_PART}, \ +IH %{MULTIPART_INVALID_HEADER_FOLDING}, \ +FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" + +# Did we see anything that might be a boundary? +# +# Here is a short description about the ModSecurity Multipart parser: the +# parser returns with value 0, if all "boundary-like" line matches with +# the boundary string which given in MIME header. In any other cases it returns +# with different value, eg. 1 or 2. +# +# The RFC 1341 descript the multipart content-type and its syntax must contains +# only three mandatory lines (above the content): +# * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING +# * --BOUNDARY_STRING +# * --BOUNDARY_STRING-- +# +# First line indicates, that this is a multipart content, second shows that +# here starts a part of the multipart content, third shows the end of content. +# +# If there are any other lines, which starts with "--", then it should be +# another boundary id - or not. +# +# After 3.0.3, there are two kinds of types of boundary errors: strict and permissive. +# +# If multipart content contains the three necessary lines with correct order, but +# there are one or more lines with "--", then parser returns with value 2 (non-zero). +# +# If some of the necessary lines (usually the start or end) misses, or the order +# is wrong, then parser returns with value 1 (also a non-zero). +# +# You can choose, which one is what you need. The example below contains the +# 'strict' mode, which means if there are any lines with start of "--", then +# ModSecurity blocked the content. But the next, commented example contains +# the 'permissive' mode, then you check only if the necessary lines exists in +# correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."), +# or other text files, which contains eg. HTTP headers. +# +# The difference is only the operator - in strict mode (first) the content blocked +# in case of any non-zero value. In permissive mode (second, commented) the +# content blocked only if the value is explicit 1. If it 0 or 2, the content will +# allowed. +# + +# +# See #1747 and #1924 for further information on the possible values for +# MULTIPART_UNMATCHED_BOUNDARY. +# +SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \ + "id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" + + +# PCRE Tuning +# We want to avoid a potential RegEx DoS condition +# +SecPcreMatchLimit 1000 +SecPcreMatchLimitRecursion 1000 + +# Some internal errors will set flags in TX and we will need to look for these. +# All of these are prefixed with "MSC_". The following flags currently exist: +# +# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded. +# +SecRule TX:/^MSC_/ "!@streq 0" \ + "id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'" + + +# -- Response body handling -------------------------------------------------- + +# Allow ModSecurity to access response bodies. +# You should have this directive enabled in order to identify errors +# and data leakage issues. +# +# Do keep in mind that enabling this directive does increases both +# memory consumption and response latency. +# +SecResponseBodyAccess On + +# Which response MIME types do you want to inspect? You should adjust the +# configuration below to catch documents but avoid static files +# (e.g., images and archives). +# +SecResponseBodyMimeType text/plain text/html text/xml + +# Buffer response bodies of up to 512 KB in length. +SecResponseBodyLimit 524288 + +# What happens when we encounter a response body larger than the configured +# limit? By default, we process what we have and let the rest through. +# That's somewhat less secure, but does not break any legitimate pages. +# +SecResponseBodyLimitAction ProcessPartial + + +# -- Filesystem configuration ------------------------------------------------ + +# The location where ModSecurity stores temporary files (for example, when +# it needs to handle a file upload that is larger than the configured limit). +# +# This default setting is chosen due to all systems have /tmp available however, +# this is less than ideal. It is recommended that you specify a location that's private. +# +SecTmpDir /tmp/ + +# The location where ModSecurity will keep its persistent data. This default setting +# is chosen due to all systems have /tmp available however, it +# too should be updated to a place that other users can't access. +# +SecDataDir /tmp/ + + +# -- File uploads handling configuration ------------------------------------- + +# The location where ModSecurity stores intercepted uploaded files. This +# location must be private to ModSecurity. You don't want other users on +# the server to access the files, do you? +# +#SecUploadDir /opt/modsecurity/var/upload/ + +# By default, only keep the files that were determined to be unusual +# in some way (by an external inspection script). For this to work you +# will also need at least one file inspection rule. +# +#SecUploadKeepFiles RelevantOnly + +# Uploaded files are by default created with permissions that do not allow +# any other user to access them. You may need to relax that if you want to +# interface ModSecurity to an external program (e.g., an anti-virus). +# +#SecUploadFileMode 0600 + + +# -- Debug log configuration ------------------------------------------------- + +# The default debug log configuration is to duplicate the error, warning +# and notice messages from the error log. +# +#SecDebugLog /opt/modsecurity/var/log/debug.log +#SecDebugLogLevel 3 + + +# -- Audit log configuration ------------------------------------------------- + +# Log the transactions that are marked by a rule, as well as those that +# trigger a server error (determined by a 5xx or 4xx, excluding 404, +# level response status codes). +# +SecAuditEngine RelevantOnly +SecAuditLogRelevantStatus "^(?:5|4(?!04))" + +# Log everything we know about a transaction. +SecAuditLogParts ABIJDEFHZ + +# Use a single file for logging. This is much easier to look at, but +# assumes that you will use the audit log only ocassionally. +# +SecAuditLogType Concurrent +SecAuditLog /var/log/modsec_audit.log + +# Specify the path for concurrent audit logging. +#SecAuditLogStorageDir /opt/modsecurity/var/audit/ + + +# -- Miscellaneous ----------------------------------------------------------- + +# Use the most commonly used application/x-www-form-urlencoded parameter +# separator. There's probably only one application somewhere that uses +# something else so don't expect to change this value. +# +SecArgumentSeparator & + +# Settle on version 0 (zero) cookies, as that is what most applications +# use. Using an incorrect cookie version may open your installation to +# evasion attacks (against the rules that examine named cookies). +# +SecCookieFormat 0 + +# Specify your Unicode Code Point. +# This mapping is used by the t:urlDecodeUni transformation function +# to properly map encoded data to your language. Properly setting +# these directives helps to reduce false positives and negatives. +# +SecUnicodeMapFile unicode.mapping 20127 + +# Improve the quality of ModSecurity by sharing information about your +# current ModSecurity version and dependencies versions. +# The following information will be shared: ModSecurity version, +# Web Server version, APR version, PCRE version, Lua version, Libxml2 +# version, Anonymous unique id for host. +SecStatusEngine On + +SecAuditLogStorageDir /var/log/audit/ diff --git a/terraform.tfstate b/terraform.tfstate index f76d13d0..1b5db8dd 100644 Binary files a/terraform.tfstate and b/terraform.tfstate differ diff --git a/terraform.tfvars b/terraform.tfvars index 2a27b3dc..4d69b841 100644 Binary files a/terraform.tfvars and b/terraform.tfvars differ