diff --git a/infra/crowdsec-envoy-bouncer/referencegrant.yaml b/infra/crowdsec-envoy-bouncer/referencegrant.yaml new file mode 100644 index 0000000..85b43cf --- /dev/null +++ b/infra/crowdsec-envoy-bouncer/referencegrant.yaml @@ -0,0 +1,16 @@ +# Allows the SecurityPolicy (in the envoy-gateway namespace) to reference the +# bouncer Service in this (crowdsec) namespace as its ext_authz backend. +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: crowdsec-ext-auth-from-envoy-gateway + namespace: crowdsec +spec: + from: + - group: gateway.envoyproxy.io + kind: SecurityPolicy + namespace: envoy-gateway + to: + - group: "" + kind: Service + name: crowdsec-envoy-bouncer diff --git a/infra/crowdsec-envoy-bouncer/values.yaml b/infra/crowdsec-envoy-bouncer/values.yaml new file mode 100644 index 0000000..73ac91a --- /dev/null +++ b/infra/crowdsec-envoy-bouncer/values.yaml @@ -0,0 +1,48 @@ +# CrowdSec Envoy bouncer, chart ghcr.io/kdwils/charts/envoy-proxy-bouncer 0.8.0 +# gRPC ext_authz service that Envoy Gateway calls for every request on protected +# listeners. Checks the client IP against LAPI decisions and (WAF) forwards the +# request to CrowdSec AppSec for inline inspection. +# +# NOTE: this is a community project ("not tested in production, use at your own +# risk"). It is used behind failOpen: true (see the SecurityPolicy), so a bouncer +# outage lets traffic through rather than blocking your services. + +# Fixed name so the SecurityPolicy backendRef / ReferenceGrant can target it. +fullnameOverride: crowdsec-envoy-bouncer + +service: + type: ClusterIP + grpcPort: 8080 + httpPort: 8081 + +config: + server: + grpcPort: 8080 + httpPort: 8081 + logLevel: "info" + + # Envoy (public proxy) is the immediate hop and, with externalTrafficPolicy: + # Local on the LB service, presents the real client IP. If you later put another + # proxy in front (e.g. Cloudflare), set trustedProxies / trustedIPHeader so the + # bouncer reads the true client IP from X-Forwarded-For instead of banning the + # proxy. + trustedProxies: [] + exemptIPs: [] + + # IP-decision enforcement against LAPI (same namespace). + bouncer: + enabled: true + lapiURL: "http://crowdsec-service:8080" + apiKeySecretRef: + name: crowdsec-secrets + key: bouncer-key + + # WAF / AppSec inline inspection (same namespace). + waf: + enabled: true + appSecURL: "http://crowdsec-appsec-service:7422" + apiKeySecretRef: + name: crowdsec-secrets + key: bouncer-key + # Chart default is false, which would deny every request if AppSec is down. + failOpen: true diff --git a/infra/crowdsec/crowdsec-namespace.yaml b/infra/crowdsec/crowdsec-namespace.yaml new file mode 100644 index 0000000..36fcbc9 --- /dev/null +++ b/infra/crowdsec/crowdsec-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: crowdsec diff --git a/infra/crowdsec/values.yaml b/infra/crowdsec/values.yaml new file mode 100644 index 0000000..93e6beb --- /dev/null +++ b/infra/crowdsec/values.yaml @@ -0,0 +1,57 @@ +# CrowdSec core — chart crowdsecurity/crowdsec 0.24.0 (appVersion v1.7.8) +# Deploys: LAPI (decisions DB), log-processing agent, and AppSec (WAF) component. +# +# Detection strategy here = CAPI community blocklists + AppSec (inline WAF). +# We intentionally do NOT parse Envoy access logs (no reliable first-party +# Envoy parser); the kdwils bouncer forwards requests to AppSec instead. +# Remediation/enforcement is done by the separate `crowdsec-envoy-bouncer`. + +#=============================# +#= Local API (LAPI) =# +#=============================# +lapi: + # Pre-register the "envoy" bouncer. The CrowdSec image turns BOUNCER_KEY_ + # into a registered bouncer named on first start. The kdwils bouncer + # authenticates with this same key. + env: + - name: BOUNCER_KEY_envoy + valueFrom: + secretKeyRef: + name: crowdsec-secrets + key: bouncer-key + persistentVolume: + data: + enabled: true + storageClassName: longhorn + size: 2Gi + config: + enabled: true + storageClassName: longhorn + size: 100Mi + +#=============================# +#= Log-processing agent =# +#=============================# +agent: + # Disabled: detection comes from AppSec + CAPI, not log tailing. Chart 0.24.0 + # refuses to render the agent DaemonSet with an empty acquisition list anyway. + enabled: false + +#=============================# +#= AppSec (WAF) component =# +#=============================# +appsec: + enabled: true + # Hub collections installed into the AppSec pod at startup (the WAF rule sets). + env: + - name: COLLECTIONS + value: "crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules" + acquisitions: + - source: appsec + listen_addr: "0.0.0.0:7422" + path: / + # Virtual-patching ruleset (CRS-based vpatch rules). Verify it loaded after + # first deploy with: cscli appsec-configs list / cscli appsec-rules list + appsec_config: crowdsecurity/virtual-patching + labels: + type: appsec diff --git a/infra/envoy-gateway/crowdsec/envoyproxy-public.yaml b/infra/envoy-gateway/crowdsec/envoyproxy-public.yaml new file mode 100644 index 0000000..532deff --- /dev/null +++ b/infra/envoy-gateway/crowdsec/envoyproxy-public.yaml @@ -0,0 +1,15 @@ +# NOT APPLIED, deliberately left out of kustomization.yaml. +# EG v1.3.2 already defaults envoyService.externalTrafficPolicy to Local, so all +# three gateway LB services preserve the real client IP without this CR. Kept as +# documentation of that finding. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: eg-public-proxy + namespace: envoy-gateway +spec: + provider: + type: Kubernetes + kubernetes: + envoyService: + externalTrafficPolicy: Local diff --git a/infra/envoy-gateway/crowdsec/gateway-public-securitypolicy.yaml b/infra/envoy-gateway/crowdsec/gateway-public-securitypolicy.yaml new file mode 100644 index 0000000..bca2010 --- /dev/null +++ b/infra/envoy-gateway/crowdsec/gateway-public-securitypolicy.yaml @@ -0,0 +1,29 @@ +# ext_authz for the whole PUBLIC gateway. Every public listener is checked +# against CrowdSec decisions and AppSec before the request reaches a backend. +# +# Scope is the entire Gateway because EG v1.3.2 rejects targetRefs.sectionName +# on SecurityPolicy ("this policy does not yet support the sectionName field"). +# Targeting the HTTPRoute instead is not an option: public routes also parent +# gateway-internal, so that would gate LAN traffic too. +# +# failOpen: true -> if the bouncer is unreachable, requests are allowed through +# (a bouncer outage must not take every public service down). +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: crowdsec-gateway-public + namespace: envoy-gateway +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-public + extAuth: + failOpen: true + grpc: + backendRefs: + - group: "" + kind: Service + name: crowdsec-envoy-bouncer + namespace: crowdsec + port: 8080