diff --git a/charts/crowdsec/README.md b/charts/crowdsec/README.md index 71e75ba..7d57707 100644 --- a/charts/crowdsec/README.md +++ b/charts/crowdsec/README.md @@ -75,6 +75,118 @@ lapi: key: dbPassword ``` +## Setup for AppSec (WAF) with Traefik bouncer as example + +Below a basic configuration for AppSec (WAF) + +``` +# your-values.yaml (option 1) +appsec: + enabled: true + acquisitions: + - source: appsec + listen_addr: "0.0.0.0:7422" + path: / + appsec_config: crowdsecurity/virtual-patching + labels: + type: appsec + env: + - name: COLLECTIONS + value: "crowdsecurity/appsec-virtual-patching" + +# This allows the LAPI pod to register and communicate with the appsec pod +config: + config.yaml.local: | + api: + server: + auto_registration: + enabled: true + token: "${REGISTRATION_TOKEN}" + allowed_ranges: + - "127.0.0.1/32" + - "192.168.0.0/16" + - "10.0.0.0/8" + - "172.16.0.0/12" +``` + +Or you can also use your own custom configurations and rules for AppSec: + +``` +# your-values.yaml (option 2) +appsec: + enabled: true + acquisitions: + - source: appsec + listen_addr: "0.0.0.0:7422" + path: / + appsec_config: crowdsecurity/crs-vpatch + labels: + type: appsec + configs: + mycustom-appsec-config.yaml: | + name: crowdsecurity/crs-vpatch + default_remediation: ban + #log_level: debug + outofband_rules: + - crowdsecurity/crs + inband_rules: + - crowdsecurity/base-config + - crowdsecurity/vpatch-* + env: + - name: COLLECTIONS + value: "crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-crs" + +# This allows the LAPI pod to register and communicate with the appsec pod +config: + config.yaml.local: | + api: + server: + auto_registration: + enabled: true + token: "${REGISTRATION_TOKEN}" + allowed_ranges: + - "127.0.0.1/32" + - "192.168.0.0/16" + - "10.0.0.0/8" + - "172.16.0.0/12" +``` + +In the traefik `values.yaml`, you need to add the following configuration: + +``` +# traefik-values.yaml +experimental: + plugins: + crowdsec-bouncer: + moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin + version: v1.3.3 +additionalArguments: + - "--entrypoints.web.http.middlewares=-crowdsec-bouncer@kubernetescrd" + - "--entrypoints.websecure.http.middlewares=-crowdsec-bouncer@kubernetescrd" + - "--providers.kubernetescrd" +``` + +And then, you can apply this middleware to your traefik ingress: + +``` +# crowdsec-bouncer-middleware.yaml +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: crowdsec-bouncer + namespace: default +spec: + plugin: + crowdsec-bouncer: + enabled: true + crowdsecMode: appsec + crowdsecAppsecEnabled: true + crowdsecAppsecHost: crowdsec-appsec-service:7422 + crowdsecLapiScheme: http + crowdsecLapiHost: crowdsec-service:8080 + crowdsecLapiKey: "" +``` + ## Values | Key | Type | Default | Description | diff --git a/charts/crowdsec/ci/crowdsec-values.yaml b/charts/crowdsec/ci/crowdsec-values.yaml index 0ba01b6..0cc9da2 100644 --- a/charts/crowdsec/ci/crowdsec-values.yaml +++ b/charts/crowdsec/ci/crowdsec-values.yaml @@ -1,3 +1,15 @@ +config: + config.yaml.local: | + api: + server: + auto_registration: + enabled: true + token: "${REGISTRATION_TOKEN}" + allowed_ranges: + - "127.0.0.1/32" + - "192.168.0.0/16" + - "10.0.0.0/8" + - "172.16.0.0/12" agent: additionalAcquisition: - source: file @@ -74,3 +86,26 @@ lapi: share_manual_decisions: true share_tainted: true share_custom: true + +appsec: + enabled: true + acquisitions: + - source: appsec + listen_addr: "0.0.0.0:7422" + path: / + appsec_config: crowdsecurity/crs-vpatch + labels: + type: appsec + configs: + mycustom-appsec-config.yaml: | + name: crowdsecurity/crs-vpatch + default_remediation: ban + #log_level: debug + outofband_rules: + - crowdsecurity/crs + inband_rules: + - crowdsecurity/base-config + - crowdsecurity/vpatch-* + env: + - name: COLLECTIONS + value: "crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-crs" diff --git a/charts/crowdsec/templates/NOTES.txt b/charts/crowdsec/templates/NOTES.txt index 1183310..4147375 100644 --- a/charts/crowdsec/templates/NOTES.txt +++ b/charts/crowdsec/templates/NOTES.txt @@ -7,6 +7,11 @@ http://{{ .Values.lapi.ingress.host }} http://{{ .Release.Name }}-service:8080 {{- end }} +{{- if .Values.appsec.enabled }} +## AppSec URL +http://{{ .Release.Name }}-appsec-service:7422 +{{- end }} + {{ if .Values.lapi.dashboard.enabled }} ## Dashboard information diff --git a/charts/crowdsec/templates/appsec-configmap.yaml b/charts/crowdsec/templates/appsec-configmap.yaml new file mode 100644 index 0000000..09f0524 --- /dev/null +++ b/charts/crowdsec/templates/appsec-configmap.yaml @@ -0,0 +1,41 @@ +# vim: set ft=gotmpl: +--- + +{{- if .Values.appsec.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: appsec-acquis-config +data: + acquis.yaml: |- + {{- range .Values.appsec.acquisitions }} + --- + {{. | toYaml | nindent 4}} + {{- end }} +{{- end }} + +--- +{{- if .Values.appsec.configs }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: "crowdsec-appsec-configs" +data: +{{ range $fileName, $content := .Values.appsec.configs -}} + {{ printf "%s: |" $fileName | indent 2 }} +{{ $content | indent 4 }} +{{ end }} +{{- end }} + +--- +{{- if .Values.appsec.rules }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: "crowdsec-appsec-rules" +data: +{{ range $fileName, $content := .Values.appsec.rules -}} + {{ printf "%s: |" $fileName | indent 2 }} +{{ $content | indent 4 }} +{{ end }} +{{- end }} \ No newline at end of file diff --git a/charts/crowdsec/templates/appsec-deployment.yaml b/charts/crowdsec/templates/appsec-deployment.yaml new file mode 100644 index 0000000..b226fd7 --- /dev/null +++ b/charts/crowdsec/templates/appsec-deployment.yaml @@ -0,0 +1,201 @@ +# vim: set ft=gotmpl: +--- + +{{- if .Values.appsec.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-appsec + labels: + k8s-app: {{ .Release.Name }} + type: appsec + version: v1 + {{- if .Values.appsec.deployAnnotations }} + annotations: + {{ toYaml .Values.appsec.deployAnnotations | trim | indent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: {{ .Release.Name }} + type: appsec + strategy: {{- toYaml .Values.appsec.strategy | nindent 4 }} + template: + metadata: + annotations: + checksum/appsec-acquis-configmap: {{ include (print $.Template.BasePath "/appsec-configmap.yaml") . | sha256sum }} + {{- if .Values.podAnnotations }} + {{- toYaml .Values.podAnnotations | trim | indent 8 }} + {{- end }} + {{- if .Values.appsec.podAnnotations }} + {{- toYaml .Values.appsec.podAnnotations | trim | indent 8 }} + {{- end }} + labels: + k8s-app: {{ .Release.Name }} + type: appsec + version: v1 + {{- if .Values.podLabels }} +{{ toYaml .Values.podLabels | trim | indent 8 }} + {{- else if .Values.appsec.podLabels }} +{{ toYaml .Values.appsec.podLabels | trim | indent 8 }} + {{- end }} + spec: + {{- with .Values.appsec.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.image.pullSecrets }} + imagePullSecrets: +{{ toYaml .Values.image.pullSecrets | indent 8 }} + {{- end }} + initContainers: + - name: wait-for-lapi-and-register + image: "{{ .Values.image.repository | default "crowdsecurity/crowdsec" }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ['sh', '-c', 'until nc "$LAPI_HOST" "$LAPI_PORT" -z; do echo waiting for lapi to start; sleep 5; done; ln -s /staging/etc/crowdsec /etc/crowdsec && cscli lapi register --machine "$USERNAME" -u $LAPI_URL --token "$REGISTRATION_TOKEN" && cp /etc/crowdsec/local_api_credentials.yaml /tmp_config/local_api_credentials.yaml'] + resources: + limits: + memory: 50Mi + requests: + cpu: 1m + memory: 10Mi + securityContext: + allowPrivilegeEscalation: false + privileged: false + volumeMounts: + - name: crowdsec-config + mountPath: /tmp_config + env: + - name: REGISTRATION_TOKEN + valueFrom: + secretKeyRef: + name: crowdsec-lapi-secrets + key: registrationToken + - name: USERNAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: LAPI_URL + value: http://{{ .Release.Name }}-service.{{ .Release.Namespace }}:8080 + - name: LAPI_HOST + value: "{{ .Release.Name }}-service.{{ .Release.Namespace }}" + - name: LAPI_PORT + value: "8080" + {{- if .Values.appsec.extraInitContainers }} + {{- toYaml .Values.appsec.extraInitContainers | nindent 6 }} + {{- end }} + containers: + - name: crowdsec-appsec + image: "{{ .Values.image.repository | default "crowdsecurity/crowdsec" }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ['sh', '-c', 'cp /tmp_config/local_api_credentials.yaml /staging/etc/crowdsec/local_api_credentials.yaml && ./docker_start.sh'] + env: + - name: DISABLE_LOCAL_API + value: "true" + - name: DISABLE_ONLINE_API + value: "true" + - name: LOCAL_API_URL + value: http://{{ .Release.Name }}-service.{{ .Release.Namespace }}:8080 + {{- with .Values.appsec.env }} + {{- toYaml . | nindent 10 }} + {{- end }} + volumeMounts: + - name: crowdsec-config + mountPath: /tmp_config + - name: appsec-acquis-config-volume + mountPath: /etc/crowdsec/acquis.yaml + subPath: acquis.yaml + {{- if .Values.appsec.configs -}} + {{- range $fileName, $content := .Values.appsec.configs }} + - name: {{ printf "crowdsec-appsec-configs-%s" (trimSuffix ".yaml" $fileName) }} + mountPath: {{ printf "/etc/crowdsec/appsec-configs/%s" $fileName }} + subPath: {{ $fileName }} + {{- end }} + {{- end }} + {{- if .Values.appsec.rules -}} + {{- range $fileName, $content := .Values.appsec.rules }} + - name: {{ printf "crowdsec-appsec-rules-%s" (trimSuffix ".yaml" $fileName) }} + mountPath: {{ printf "/etc/crowdsec/appsec-rules/%s" $fileName }} + subPath: {{ $fileName }} + {{- end }} + {{- end }} + resources: + {{- toYaml .Values.appsec.resources | nindent 10 }} + ports: + - name: appsec + containerPort: 7422 + protocol: TCP + {{- if .Values.appsec.metrics.enabled }} + - name: metrics + containerPort: 6060 + protocol: TCP + {{- end }} + + livenessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + tcpSocket: + port: appsec + readinessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + tcpSocket: + port: appsec + startupProbe: + failureThreshold: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + tcpSocket: + port: appsec + + securityContext: + allowPrivilegeEscalation: false + privileged: false + + terminationGracePeriodSeconds: 30 + + {{- with .Values.appsec.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.appsec.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.appsec.priorityClassName }} + priorityClassName: {{ .Values.appsec.priorityClassName }} + {{- end}} + + volumes: + - name: appsec-acquis-config-volume + configMap: + name: appsec-acquis-config + - name: crowdsec-config + emptyDir: {} + {{- if .Values.appsec.configs -}} + {{- range $fileName, $content := .Values.appsec.configs }} + - name: {{ printf "crowdsec-appsec-configs-%s" (trimSuffix ".yaml" $fileName) }} + configMap: + name: "crowdsec-appsec-configs" + items: + - key: {{ $fileName }} + path: {{ $fileName }} + {{- end }} + {{- end }} + {{- if .Values.appsec.rules -}} + {{- range $fileName, $content := .Values.appsec.rules }} + - name: {{ printf "crowdsec-appsec-rules-%s" (trimSuffix ".yaml" $fileName) }} + configMap: + name: "crowdsec-appsec-rules" + items: + - key: {{ $fileName }} + path: {{ $fileName }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/crowdsec/templates/appsec-service.yaml b/charts/crowdsec/templates/appsec-service.yaml new file mode 100644 index 0000000..e4ffe4c --- /dev/null +++ b/charts/crowdsec/templates/appsec-service.yaml @@ -0,0 +1,45 @@ +# vim: set ft=gotmpl: +--- +{{- if .Values.appsec.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-appsec-service + labels: + app: {{ .Release.Name }}-appsec-service + {{- with .Values.appsec.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.appsec.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.appsec.service.type }} + {{- with .Values.appsec.service.loadBalancerIP }} + loadBalancerIP: {{ . }} + {{- end }} + {{- with .Values.appsec.service.loadBalancerClass }} + loadBalancerClass: {{ . }} + {{- end }} + {{- with .Values.appsec.service.externalIPs }} + externalIPs: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if or (eq .Values.appsec.service.type "LoadBalancer") (eq .Values.appsec.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.appsec.service.externalTrafficPolicy | quote }} + {{- end }} + ports: + - port: 6060 + targetPort: 6060 + protocol: TCP + name: metrics + - port: 7422 + targetPort: 7422 + protocol: TCP + name: appsec + selector: + k8s-app: {{ .Release.Name }} + type: appsec + version: v1 +{{- end }} \ No newline at end of file diff --git a/charts/crowdsec/templates/appsec-serviceMonitor.yaml b/charts/crowdsec/templates/appsec-serviceMonitor.yaml new file mode 100644 index 0000000..2ebf7d4 --- /dev/null +++ b/charts/crowdsec/templates/appsec-serviceMonitor.yaml @@ -0,0 +1,29 @@ +# vim: set ft=gotmpl: +--- + +{{- if and (.Values.appsec.enabled) (.Values.appsec.metrics.enabled) (.Values.appsec.metrics.serviceMonitor.enabled) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ .Release.Name }}-appsec-service + namespace: {{ .Release.Namespace }} + {{- if .Values.appsec.metrics.serviceMonitor.additionalLabels }} + labels: +{{ toYaml .Values.appsec.metrics.serviceMonitor.additionalLabels | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-appsec-service + namespaceSelector: + matchNames: [{{ .Release.Namespace }}] + attachMetadata: + node: true + endpoints: + - port: metrics + relabelings: + - action: replace + sourceLabels: + - __meta_kubernetes_pod_node_name + targetLabel: machine +{{ end }} diff --git a/charts/crowdsec/templates/lapi-deployment.yaml b/charts/crowdsec/templates/lapi-deployment.yaml index 0d951f5..4d63c2b 100644 --- a/charts/crowdsec/templates/lapi-deployment.yaml +++ b/charts/crowdsec/templates/lapi-deployment.yaml @@ -125,6 +125,11 @@ spec: secretKeyRef: name: crowdsec-lapi-secrets key: csLapiSecret + - name: REGISTRATION_TOKEN + valueFrom: + secretKeyRef: + name: crowdsec-lapi-secrets + key: registrationToken - name: CUSTOM_HOSTNAME valueFrom: fieldRef: diff --git a/charts/crowdsec/templates/lapi-secrets.yaml b/charts/crowdsec/templates/lapi-secrets.yaml index c001612..67efe1d 100644 --- a/charts/crowdsec/templates/lapi-secrets.yaml +++ b/charts/crowdsec/templates/lapi-secrets.yaml @@ -12,6 +12,7 @@ metadata: type: Opaque data: csLapiSecret: {{ include "lapi.csLapiSecret" . | b64enc }} + registrationToken: {{ randAlphaNum 48 | b64enc }} {{- with .Values.lapi.extraSecrets }} {{- range $key, $value := . }} {{ $key }}: {{ $value | b64enc }} diff --git a/charts/crowdsec/templates/tests/test_appsec_up.yaml b/charts/crowdsec/templates/tests/test_appsec_up.yaml new file mode 100644 index 0000000..30c42fb --- /dev/null +++ b/charts/crowdsec/templates/tests/test_appsec_up.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-test-appsec-up" + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + containers: + - name: {{ .Release.Name }}-test-appsec-up + image: curlimages/curl + command: + - /bin/sh + - -ec + - | + curl -XGET http://{{ .Release.Name }}-appsec-service:7422/ + curl -XGET http://{{ .Release.Name }}-appsec-service:6060/metrics + restartPolicy: Never \ No newline at end of file diff --git a/charts/crowdsec/values.yaml b/charts/crowdsec/values.yaml index 35d5c27..1422c1c 100644 --- a/charts/crowdsec/values.yaml +++ b/charts/crowdsec/values.yaml @@ -465,4 +465,115 @@ agent: # -- docker image tag tag: "1.28" -#service: {} +# -- Enable AppSec (https://docs.crowdsec.net/docs/next/appsec/intro) +appsec: + # -- Enable AppSec (by default disabled) + enabled: false + # -- Additional acquisitions for AppSec + acquisitions: [] + #- source: appsec + # listen_addr: "0.0.0.0:7422" + # path: / + # appsec_config: crowdsecurity/virtual-patching + # labels: + # type: appsec + # -- appsec_configs (https://docs.crowdsec.net/docs/next/appsec/configuration): key is the filename, value is the config content + configs: {} + #mycustom-appsec-config.yaml: | + # name: crowdsecurity/crs-vpatch + # default_remediation: ban + # #log_level: debug + # outofband_rules: + # - crowdsecurity/crs + # inband_rules: + # - crowdsecurity/base-config + # - crowdsecurity/vpatch-* + # -- appsec_configs to disable + # -- appsec_rules (https://docs.crowdsec.net/docs/next/appsec/rules_syntax) + rules: {} + #mycustom-appsec-rule.yaml: | + # name: crowdsecurity/example-rule + # description: "Detect example pattern" + # rules: + # - zones: + # - URI + # transform: + # - lowercase + # match: + # type: contains + # value: this-is-a-appsec-rule-test + # labels: + # type: exploit + # service: http + # behavior: "http:exploit" + # confidence: 3 + # spoofable: 0 + # label: "A good description of the rule" + # classification: + # - cve.CVE-xxxx-xxxxx + # - attack.Txxxx + + # -- environment variables + env: + # -- COLLECTIONS to install, separated by space (value: "crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-crs") + #- name: COLLECTIONS + # value: "crowdsecurity/appsec-virtual-patching" + # -- APPSEC_CONFIGS files to install, separated by space (value: "crowdsecurity/config-1 crowdsecurity/config-2") + #- name: APPSEC_CONFIGS + # value: "crowdsecurity/appsec-default" + # -- APPSEC_RULES files to install, separated by space (value: "crowdsecurity/rules-1 crowdsecurity/rules-2") + #- name: APPSEC_RULES + # value: "" + # -- DISABLE_APPSEC_RULES files to disable, separated by space (value: "crowdsecurity/rules-1 crowdsecurity/rules-2") + #- name: DISABLE_APPSEC_RULES + # value: "" + # -- DISABLE_APPSEC_CONFIGS files to disable, separated by space (value: "crowdsecurity/config-1 crowdsecurity/config-2") + #- name: DISABLE_APPSEC_CONFIGS + # value: "" + + # -- appsec deployment annotations + deployAnnotations: {} + # -- strategy for appsec deployment + strategy: + type: Recreate + # -- podAnnotations for appsec deployment + podAnnotations: {} + # -- podLabels for appsec deployment + podLabels: {} + # -- tolerations for appsec deployment + tolerations: [] + # -- nodeSelector for appsec deployment + nodeSelector: {} + # -- affinity for appsec deployment + affinity: {} + # -- priorityClassName for appsec deployment + priorityClassName: "" + # -- extraInitContainers for appsec deployment + extraInitContainers: [] + # -- resources for appsec deployment + resources: + limits: + memory: 250Mi + cpu: 500m + requests: + cpu: 500m + memory: 250Mi + # -- Enable service monitoring (exposes "metrics" port "6060" for Prometheus and "7422" for AppSec) + metrics: + enabled: true + # -- Creates a ServiceMonitor so Prometheus will monitor this service + # -- Prometheus needs to be configured to watch on all namespaces for ServiceMonitors + # -- See the documentation: https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack#prometheusioscrape + # -- See also: https://github.com/prometheus-community/helm-charts/issues/106#issuecomment-700847774 + serviceMonitor: + enabled: false + additionalLabels: {} + + service: + type: ClusterIP + labels: {} + annotations: {} + externalIPs: [] + loadBalancerIP: null + loadBalancerClass: null + externalTrafficPolicy: Cluster \ No newline at end of file