A Helm chart for deploying the Cloudflare Access Webhook Redirect service. This service acts as an authentication proxy that validates requests using Cloudflare Access Service Auth tokens before forwarding them to target backend services.
This is particularly useful for protecting webhook endpoints or APIs that don't have built-in authentication, by leveraging Cloudflare Access for secure authentication and authorization.
- Kubernetes 1.19+
- Helm 3.0+
- A Cloudflare Access application with Service Auth credentials
- Target backend service to proxy requests to
helm repo add timschoenle https://timschoenle.github.io/helm-charts
helm repo updatehelm install [RELEASE_NAME] timschoenle/cloudflare-access-webhook-redirect \
--namespace [NAMESPACE] \
--create-namespace \
--set application.handler.targetBase="http://backend:8080" \
--set application.cloudflareAccess.clientId="your-client-id" \
--set application.cloudflareAccess.clientSecret="your-client-secret"helm upgrade [RELEASE_NAME] timschoenle/cloudflare-access-webhook-redirect \
--namespace [NAMESPACE]helm uninstall [RELEASE_NAME] --namespace [NAMESPACE]The following table lists the configurable parameters of the chart and their default values.
| Key | Type | Default | Description |
|---|---|---|---|
| affinity | object | {} |
Pod affinity rules |
| application.cloudflareAccess.secretName | string | "" |
Existing secret name containing Cloudflare Access credentials Must contain client_id and client_secret keys |
| application.handler.paths | object | {} |
Path configurations with allowed HTTP methods Example: api/webhook: - ALL test: - GET - POST |
| application.handler.targetBase | string | "" |
Base URL for redirect targets |
| application.logLevel | string | "info" |
Application log level |
| application.sentryDsn | string | "" |
Sentry DSN for error tracking (empty disables) |
| application.server.host | string | "0.0.0.0" |
Server bind address |
| application.server.port | int | 8080 |
HTTP server port |
| autoscaling.enabled | bool | false |
Enable Horizontal Pod Autoscaler (HPA) |
| autoscaling.maxReplicas | int | 5 |
Maximum replicas |
| autoscaling.minReplicas | int | 1 |
Minimum replicas |
| autoscaling.targetCPUUtilizationPercentage | int | 80 |
Target CPU utilization (%) |
| autoscaling.targetMemoryUtilizationPercentage | int | 80 |
Target memory utilization (%) |
| fullnameOverride | string | "" |
Override the full release name |
| image.pullPolicy | string | "IfNotPresent" |
Image pull policy |
| image.repository | string | "timmi6790/cloudflare-access-webhook-redirect" |
Container image repository (e.g. docker.io/user/image) |
| image.tag | string | "v0.3.21@sha256:e5607cedf22b71b8835146aaa1c3e274102888d3d85890224ace3bf088aa646a" |
Container image tag (version) |
| imagePullSecrets | list | [] |
Optional image pull secrets for private registries |
| ingress.annotations | object | {} |
Additional ingress annotations Example: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/rate-limit: "100" |
| ingress.enabled | bool | false |
Enable ingress resource |
| ingress.hosts | list | [] |
Host definitions for ingress Example: - host: example.local paths: - path: / pathType: Prefix |
| ingress.ingressClassName | string | "nginx" |
Ingress class name (e.g. nginx) |
| ingress.tls | list | [] |
TLS configuration for ingress Example: - secretName: example-tls hosts: - example.local |
| livenessProbe.enabled | bool | true |
Enable liveness probe |
| livenessProbe.failureThreshold | int | 3 |
Failure threshold |
| livenessProbe.httpGet.path | string | "/health" |
Health check path |
| livenessProbe.httpGet.port | string | "http" |
Health check port |
| livenessProbe.initialDelaySeconds | int | 10 |
Initial delay before probe starts |
| livenessProbe.periodSeconds | int | 10 |
Probe frequency |
| livenessProbe.timeoutSeconds | int | 5 |
Probe timeout |
| nameOverride | string | "" |
Override the chart name |
| networkPolicy | object | {"egress":{"customRules":[],"dns":{"enabled":true},"enabled":true,"http":{"enabled":false},"https":{"enabled":false},"sentry":{"enabled":true}},"enabled":false,"ingress":{"controller":{"enabled":true,"namespace":"traefik","selector":{"app.kubernetes.io/name":"traefik"}},"customRules":[],"enabled":true,"monitoring":{"enabled":true,"namespace":"monitoring"}}} |
Network policy configuration |
| networkPolicy.egress | object | {"customRules":[],"dns":{"enabled":true},"enabled":true,"http":{"enabled":false},"https":{"enabled":false},"sentry":{"enabled":true}} |
Egress configuration |
| networkPolicy.egress.customRules | list | [] |
Custom egress rules |
| networkPolicy.egress.dns | object | {"enabled":true} |
DNS configuration for egress |
| networkPolicy.egress.dns.enabled | bool | true |
Allow egress to DNS |
| networkPolicy.egress.enabled | bool | true |
Enable egress rules |
| networkPolicy.egress.http | object | {"enabled":false} |
HTTP configuration for egress |
| networkPolicy.egress.http.enabled | bool | false |
Allow egress to HTTP (TCP/80) |
| networkPolicy.egress.https | object | {"enabled":false} |
HTTPS configuration for egress |
| networkPolicy.egress.https.enabled | bool | false |
Allow egress to HTTPS (TCP/443) |
| networkPolicy.egress.sentry | object | {"enabled":true} |
Sentry configuration for egress |
| networkPolicy.egress.sentry.enabled | bool | true |
Allow egress to Sentry (HTTPS) |
| networkPolicy.enabled | bool | false |
Enable network policies |
| networkPolicy.ingress | object | {"controller":{"enabled":true,"namespace":"traefik","selector":{"app.kubernetes.io/name":"traefik"}},"customRules":[],"enabled":true,"monitoring":{"enabled":true,"namespace":"monitoring"}} |
Ingress configuration |
| networkPolicy.ingress.controller | object | {"enabled":true,"namespace":"traefik","selector":{"app.kubernetes.io/name":"traefik"}} |
Ingress Controller configuration |
| networkPolicy.ingress.controller.enabled | bool | true |
Allow ingress from Ingress Controller |
| networkPolicy.ingress.controller.namespace | string | "traefik" |
Namespace where Ingress Controller is running (default: traefik) |
| networkPolicy.ingress.controller.selector | object | {"app.kubernetes.io/name":"traefik"} |
Pod selector for Ingress Controller (default: Traefik label) |
| networkPolicy.ingress.customRules | list | [] |
Custom ingress rules |
| networkPolicy.ingress.enabled | bool | true |
Enable ingress rules |
| networkPolicy.ingress.monitoring | object | {"enabled":true,"namespace":"monitoring"} |
Monitoring configuration for ingress |
| networkPolicy.ingress.monitoring.enabled | bool | true |
Allow ingress from monitoring namespace |
| networkPolicy.ingress.monitoring.namespace | string | "monitoring" |
Namespace where monitoring tools are running |
| nodeSelector | object | {} |
Node selector labels for scheduling |
| podAnnotations | object | {} |
Additional annotations for the Pod metadata |
| podDisruptionBudget.enabled | bool | false |
Enable PodDisruptionBudget |
| podDisruptionBudget.maxUnavailable | int | 1 |
Maximum unavailable pods |
| podDisruptionBudget.minAvailable | int | 1 |
Minimum available pods |
| podLabels | object | {} |
Additional labels for the Pod metadata |
| podSecurityContext.fsGroup | int | 10001 |
Group ID for file system access |
| podSecurityContext.runAsNonRoot | bool | true |
Run pod as non-root user |
| podSecurityContext.runAsUser | int | 10001 |
User ID to run as |
| priorityClassName | string | "" |
Optional Kubernetes PriorityClass name |
| readinessProbe.enabled | bool | true |
Enable readiness probe |
| readinessProbe.failureThreshold | int | 3 |
Failure threshold |
| readinessProbe.httpGet.path | string | "/health" |
Health check path |
| readinessProbe.httpGet.port | string | "http" |
Health check port |
| readinessProbe.initialDelaySeconds | int | 5 |
Initial delay before probe starts |
| readinessProbe.periodSeconds | int | 5 |
Probe frequency |
| readinessProbe.timeoutSeconds | int | 3 |
Probe timeout |
| replicaCount | int | 1 |
Number of replicas to deploy |
| resources.limits.cpu | string | "100m" |
Maximum CPU usage (e.g. 100m = 0.1 core) |
| resources.limits.memory | string | "50Mi" |
Maximum memory usage (e.g. 64Mi) |
| resources.requests.cpu | string | "10m" |
Guaranteed CPU request |
| resources.requests.memory | string | "35Mi" |
Guaranteed memory request |
| securityContext.allowPrivilegeEscalation | bool | false |
Allow privilege escalation |
| securityContext.capabilities.drop | list | ["ALL"] |
Linux capabilities to drop |
| securityContext.readOnlyRootFilesystem | bool | false |
Mount root filesystem as read-only |
| service.annotations | object | {} |
Additional service annotations |
| service.port | int | 80 |
Service port |
| service.type | string | "ClusterIP" |
Kubernetes service type |
| serviceAccount.annotations | object | {} |
Additional annotations for the service account |
| serviceAccount.automountToken | bool | false |
Whether to automount the service account token |
| serviceAccount.create | bool | true |
Whether to create a dedicated service account |
| serviceAccount.name | string | "" |
Custom service account name (auto-generated if empty) |
| startupProbe.enabled | bool | true |
Enable startup probe |
| startupProbe.failureThreshold | int | 30 |
Failure threshold |
| startupProbe.httpGet.path | string | "/health" |
Health check path |
| startupProbe.httpGet.port | string | "http" |
Health check port |
| startupProbe.initialDelaySeconds | int | 2 |
Initial delay before probe starts |
| startupProbe.periodSeconds | int | 5 |
Probe frequency |
| startupProbe.successThreshold | int | 1 |
Success threshold |
| startupProbe.timeoutSeconds | int | 3 |
Probe timeout |
| tolerations | list | [] |
Tolerations for taints |
| topologySpreadConstraints | list | [] |
Pod topology spread constraints for availability |
| volumeMounts | list | [] |
Additional volume mounts (e.g., /cache) |
| volumes | list | [] |
Additional volumes (e.g., cache, tmp) |
Create a Kubernetes secret with your Cloudflare Access Service Auth credentials:
kubectl create secret generic cloudflare-access-secret \
--namespace [NAMESPACE] \
--from-literal=client_id='your-client-id' \
--from-literal=client_secret='your-client-secret'Then reference it in your values:
application:
cloudflareAccess:
secretName: "cloudflare-access-secret"application:
cloudflareAccess:
clientId: "your-client-id"
clientSecret: "your-client-secret"Define which paths should be proxied and which HTTP methods are allowed:
application:
handler:
paths:
# Allow all HTTP methods for webhook endpoint
api/webhook:
- ALL
# Allow specific methods
api/data:
- GET
- POST
# Multiple endpoints
health:
- GET
metrics:
- GETAvailable HTTP methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, or ALL for all methods.
application:
handler:
targetBase: "http://backend-service:8080"
paths:
api/webhook:
- POST
cloudflareAccess:
clientId: "your-client-id-here"
clientSecret: "your-client-secret-here"application:
handler:
targetBase: "http://backend-service:8080"
paths:
api/webhook:
- POST
api/status:
- GET
cloudflareAccess:
secretName: "cloudflare-access-secret"
ingress:
enabled: true
ingressClassName: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: webhook.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: webhook-tls
hosts:
- webhook.example.comreplicaCount: 3
application:
handler:
targetBase: "http://backend-service:8080"
paths:
api/webhook:
- POST
api/events:
- POST
- GET
cloudflareAccess:
secretName: "cloudflare-access-secret"
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: cloudflare-access-webhook-redirect
podDisruptionBudget:
enabled: true
minAvailable: 2
resources:
limits:
cpu: 200m
memory: 30Mi
requests:
cpu: 50m
memory: 20Miapplication:
handler:
targetBase: "http://backend-service:8080"
paths:
api/webhook:
- ALL
cloudflareAccess:
secretName: "cloudflare-access-secret"
networkPolicy:
enabled: true
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 8080
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443replicaCount: 2
application:
logLevel: info
sentryDsn: "https://your-sentry-dsn@sentry.io/project"
handler:
targetBase: "http://backend-service:8080"
paths:
api/webhook:
- POST
api/data:
- GET
- POST
cloudflareAccess:
secretName: "cloudflare-access-secret"
resources:
limits:
cpu: 100m
memory: 15Mi
requests:
cpu: 10m
memory: 10Mi
startupProbe:
enabled: true
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30
livenessProbe:
enabled: true
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
enabled: true
initialDelaySeconds: 5
periodSeconds: 5
podDisruptionBudget:
enabled: true
minAvailable: 1- Client sends a request to the service (e.g., via Ingress)
- The service validates the request against the configured paths and HTTP methods
- It uses Cloudflare Access Service Auth credentials to authenticate the request
- If authenticated, the request is proxied to the target backend service
- The response from the backend is returned to the client
This provides a zero-trust authentication layer for services that don't have built-in authentication.
- Always use secrets for Cloudflare Access credentials in production
- Enable NetworkPolicy to restrict ingress/egress traffic
- Use TLS/HTTPS via Ingress with proper certificates
- Monitor logs and consider enabling Sentry for error tracking
- Set resource limits to prevent resource exhaustion
- Use PodDisruptionBudget for high-availability deployments
- https://github.com/TimSchoenle/cloudflare-access-webhook-redirect
- https://github.com/TimSchoenle/helm-charts
| Name | Url | |
|---|---|---|
| Tim Schönle | https://github.com/TimSchoenle |
Autogenerated from chart metadata using helm-docs v1.14.2