diff --git a/helm/Chart.yaml b/helm/Chart.yaml index a8f58d8..55976fb 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: socket-firewall description: Socket.dev Registry Firewall - Block vulnerable packages before they reach your cluster type: application -version: 0.3.1 +version: 0.4.0 appVersion: "1.1.328" keywords: - security diff --git a/helm/README.md b/helm/README.md index 2422e09..b19cddc 100644 --- a/helm/README.md +++ b/helm/README.md @@ -123,8 +123,20 @@ registries: | `autoscaling.enabled` | Enable HorizontalPodAutoscaler | `false` | | `podDisruptionBudget.enabled` | Keep pods available during node maintenance | `true` | | `extraContainers` | Sidecar containers (auth proxies, log collectors) | `[]` | -| `resources.limits.cpu` | CPU limit | `1` | -| `resources.limits.memory` | Memory limit | `768Mi` | +| `resources.limits.cpu` | CPU limit | `4` | +| `resources.limits.memory` | Memory limit | `8Gi` | +| `terminationGracePeriodSeconds` | Pod grace period; set ≥ `forwardProxy.maxTunnelLifetimeSeconds` when CONNECT is enabled | `""` (30s) | +| **Forward Proxy (HTTP CONNECT)** | _CASB CONNECT tunnels — see [section below](#forward-proxy-http-connect)_ | | +| `forwardProxy.enabled` | Enable the CONNECT listener (requires image ≥ 1.1.275) | `false` | +| `forwardProxy.port` | CONNECT listener port | `3128` | +| `forwardProxy.maxTunnelLifetimeSeconds` | Hard cap on a single tunnel's lifetime | `600` | +| `forwardProxy.maxConnectionsPerSource` | Per-source-IP concurrent tunnel cap | `64` | +| `forwardProxy.proxyProtocolPort` | Internal loopback PROXY-protocol port | `8081` | +| `forwardProxy.skipStreamLuaCheck` | Bypass nginx stream-lua capability check (custom images only) | `false` | +| `forwardProxy.service.enabled` | Create a dedicated L4 Service for CONNECT (required to expose it externally) | `false` | +| `forwardProxy.service.type` | `LoadBalancer` (NLB) or `NodePort` — **not** behind an ALB/L7 ingress | `LoadBalancer` | +| `forwardProxy.service.annotations` | Annotations for the L4 Service (e.g. AWS NLB) | `{}` | +| `forwardProxy.service.loadBalancerSourceRanges` | CIDRs allowed to reach the CONNECT listener (your CASB egress) | `[]` | | **Security** | | | | `securityContext` | Container security context | PSS restricted (see values.yaml) | | `podSecurityContext` | Pod-level security context | `{}` | @@ -338,6 +350,9 @@ ingress: ### AWS ALB Ingress +> An ALB cannot carry the HTTP CONNECT method. For CASB CONNECT tunnels, see +> [Forward Proxy (HTTP CONNECT)](#forward-proxy-http-connect). + ```yaml ingress: enabled: true @@ -380,6 +395,30 @@ ingress: - pypi.org ``` +## Forward Proxy (HTTP CONNECT) + +Some CASBs (e.g. Netskope or Zscaler in proxy-chaining mode) reach upstream +proxies via an HTTP `CONNECT` tunnel instead of a standard HTTPS request. Enable +the firewall's CONNECT listener with `forwardProxy.enabled` (requires image +≥ 1.1.275). + +Because `CONNECT` is a raw TCP tunnel, it cannot pass through a Layer-7 Ingress +(nginx, Traefik, AWS ALB) — those terminate TLS and parse HTTP. Expose it with a +**Layer-4 (TCP passthrough) load balancer** by setting +`forwardProxy.service.enabled=true`, which creates a dedicated Service for the +CONNECT port. Your existing Ingress/Service keeps serving normal HTTPS traffic. + +```yaml +forwardProxy: + enabled: true + service: + enabled: true + type: LoadBalancer # must be L4 (TCP passthrough), not an L7 ingress +``` + +See [`examples/forward-proxy.yaml`](examples/forward-proxy.yaml) for a complete +example. + ## TLS Configuration ### Self-Signed (Default) diff --git a/helm/examples/forward-proxy.yaml b/helm/examples/forward-proxy.yaml new file mode 100644 index 0000000..a59db5f --- /dev/null +++ b/helm/examples/forward-proxy.yaml @@ -0,0 +1,37 @@ +# Forward Proxy (HTTP CONNECT) example — for CASBs (e.g. Netskope/Zscaler in +# proxy-chaining mode) that reach upstreams via a CONNECT tunnel. Exposes the +# CONNECT port on a Layer-4 (TCP passthrough) Service, since CONNECT cannot +# traverse an L7 Ingress. +# +# Usage: +# helm install socket-firewall . -f examples/forward-proxy.yaml \ +# --set socket.apiToken=$SOCKET_API_TOKEN \ +# --set pathRouting.domain=sfw.yourcompany.com + +socket: + apiToken: "" # set via --set socket.apiToken=xxx + +# Clients still target the firewall's own hostname/paths as usual. +pathRouting: + enabled: true + domain: sfw.yourcompany.com # override with --set + routes: + - path: /npm + upstream: https://registry.npmjs.org + registry: npm + - path: /pypi + upstream: https://pypi.org + registry: pypi + +forwardProxy: + enabled: true + service: + enabled: true + type: LoadBalancer # must be L4 (TCP passthrough) + annotations: {} # provider-specific L4 LB annotations, if any + # Restrict to your CASB egress ranges (recommended). + loadBalancerSourceRanges: [] + +# Give in-flight tunnels time to drain on rollout/scale-down +# (set >= forwardProxy.maxTunnelLifetimeSeconds). +terminationGracePeriodSeconds: 660 diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt index afb4e5d..f28c7a6 100644 --- a/helm/templates/NOTES.txt +++ b/helm/templates/NOTES.txt @@ -99,6 +99,26 @@ Configure package managers to use your custom domains: {{- end }} {{- end }} +{{- if .Values.forwardProxy.enabled }} + +## Forward Proxy (HTTP CONNECT) + +CONNECT listener enabled on port {{ .Values.forwardProxy.port }} — point your CASB here. +Expose it via a Layer-4 (TCP passthrough) load balancer; it cannot go through an Ingress. +{{- if .Values.forwardProxy.service.enabled }} + + kubectl get svc {{ include "socket-firewall.fullname" . }}-connect -n {{ .Release.Namespace }} +{{- else }} + + Set forwardProxy.service.enabled=true to create the Layer-4 Service for external access. +{{- end }} +{{- if not .Values.terminationGracePeriodSeconds }} + + Tip: set terminationGracePeriodSeconds >= forwardProxy.maxTunnelLifetimeSeconds so + in-flight tunnels drain on rollout/scale-down. +{{- end }} +{{- end }} + ## Verify Test the health endpoint: diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index cc4c53c..0b510d3 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -22,6 +22,9 @@ spec: {{- include "socket-firewall.selectorLabels" . | nindent 8 }} spec: serviceAccountName: {{ include "socket-firewall.serviceAccountName" . }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -202,6 +205,10 @@ spec: value: {{ (.Values.service.containerHttpsPort | default .Values.service.httpsPort) | quote }} - name: CONFIG_FILE value: /app/socket.yml + {{- if and .Values.forwardProxy.enabled .Values.forwardProxy.skipStreamLuaCheck }} + - name: SOCKET_SKIP_STREAM_LUA_CHECK + value: "1" + {{- end }} {{- if and .Values.redis.enabled (or .Values.redis.password .Values.redis.existingSecret) }} - name: REDIS_PASSWORD valueFrom: diff --git a/helm/templates/service-forward-proxy.yaml b/helm/templates/service-forward-proxy.yaml new file mode 100644 index 0000000..7451d69 --- /dev/null +++ b/helm/templates/service-forward-proxy.yaml @@ -0,0 +1,34 @@ +{{- if and .Values.forwardProxy.enabled .Values.forwardProxy.service.enabled }} +{{- /* + Dedicated Service for the CONNECT listener. CONNECT cannot traverse an L7 + Ingress, so this must be a Layer-4 (TCP passthrough) load balancer + (type LoadBalancer or NodePort). +*/}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "socket-firewall.fullname" . }}-connect + labels: + {{- include "socket-firewall.labels" . | nindent 4 }} + app.kubernetes.io/component: forward-proxy + {{- with .Values.forwardProxy.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.forwardProxy.service.type }} + {{- with .Values.forwardProxy.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml . | nindent 4 }} + {{- end }} + ports: + - port: {{ .Values.forwardProxy.port }} + targetPort: forward-proxy + protocol: TCP + name: forward-proxy + {{- if and (eq .Values.forwardProxy.service.type "NodePort") .Values.forwardProxy.service.nodePort }} + nodePort: {{ .Values.forwardProxy.service.nodePort }} + {{- end }} + selector: + {{- include "socket-firewall.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index d3a901e..7d7d316 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -215,15 +215,31 @@ clientIp: # Walk the header to find the originating IP recursive: true -# HTTP CONNECT forward-proxy listener (for CASB-shaped traffic). -# Requires OpenResty / nginx with stream{} + ngx_stream_lua_module. -# When enabled, the chart exposes the CONNECT port on the container and Service. +# HTTP CONNECT forward-proxy listener, for CASBs (e.g. Netskope/Zscaler in +# proxy-chaining mode) that reach upstreams via a CONNECT tunnel. Requires the +# official image (>= 1.1.275). See README "Forward Proxy (HTTP CONNECT)". forwardProxy: enabled: false port: 3128 + # Hard cap on a single tunnel's lifetime (seconds). maxTunnelLifetimeSeconds: 600 + # Per-source-IP concurrent tunnel cap. maxConnectionsPerSource: 64 + # Internal loopback PROXY-protocol port (not exposed). Must differ from port. proxyProtocolPort: 8081 + # Set true only for a custom image you've confirmed supports stream-lua. + skipStreamLuaCheck: false + # Dedicated Service for the CONNECT port. CONNECT cannot pass through an L7 + # Ingress, so this must be a Layer-4 (TCP passthrough) load balancer. + service: + enabled: false + type: LoadBalancer + # Provider-specific L4 load balancer annotations, if any. + annotations: {} + # Fixed nodePort (30000-32767) when type=NodePort. Empty = auto-assign. + nodePort: "" + # Restrict source CIDRs that may reach the listener (e.g. CASB egress). + loadBalancerSourceRanges: [] # Metadata filtering (removes blocked/warned packages from registry metadata responses) metadataFiltering: @@ -372,7 +388,10 @@ resources: cpu: "2" memory: 8Gi -# Health check configuration +# Health check configuration. +# Note: the CONNECT listener (forwardProxy) shares the same nginx master +# process as the HTTP/HTTPS listeners, so this /health probe also covers it +# — if :3128 fails to bind, nginx won't start and the probe fails too. healthCheck: enabled: true path: /health @@ -381,6 +400,11 @@ healthCheck: timeoutSeconds: 10 failureThreshold: 3 +# Pod termination grace period (seconds). Empty = Kubernetes default (30s). +# When forwardProxy is enabled, set this >= forwardProxy.maxTunnelLifetimeSeconds +# so in-flight CONNECT tunnels aren't killed mid-stream on rollout/scale-down. +terminationGracePeriodSeconds: "" + # Pod annotations podAnnotations: {}