Security¶
This document covers the security policy and best practices for the Cloudflare Tunnel Gateway Controller.
Supported Versions¶
| Version | Supported |
|---|---|
| 3.x.x | Yes |
| 2.x.x | No |
Reporting Vulnerabilities¶
Do Not Use Public Issues
Please do not report security vulnerabilities through public GitHub issues.
Report vulnerabilities via email:
- Email: f@lex.la
- GPG Key:
F57F 85FC 7975 F22B BC3F 2504 9C17 3EB1 B531 AA1F
What to Include¶
- Type of vulnerability
- Full paths of affected source files
- Location of affected source code (tag/branch/commit)
- Step-by-step reproduction instructions
- Proof-of-concept or exploit code (if possible)
- Impact assessment
Response Timeline¶
| Stage | Timeline |
|---|---|
| Initial Response | Within 48 hours |
| Status Update | Within 7 days |
| Fix Timeline | Depends on severity |
Security Best Practices¶
API Token Management¶
The Cloudflare API token is sensitive and should be:
-
Stored in Kubernetes Secret
-
Scoped with minimum permissions
-
Account: Cloudflare Tunnel (Edit, Read)
-
Rotated regularly
- Create new token in Cloudflare dashboard
- Update Kubernetes secret
-
Controller picks up new token on restart
-
Never committed to git
- Use external secret management (Vault, AWS Secrets Manager)
RBAC Configuration¶
The controller requires specific Kubernetes permissions:
# Minimum required permissions (v3) -- matches charts/.../templates/clusterrole.yaml
rules:
# Gateway API - read specs
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes", "grpcroutes", "referencegrants", "backendtlspolicies", "listenersets"]
verbs: ["get", "list", "watch"]
# GatewayClasses - the controller manages the spec-defined gateway-exists
# finalizer (metadata write outside the status subresource)
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gatewayclasses"]
verbs: ["get", "list", "watch", "update", "patch"]
# Gateways - status patches require update/patch on the parent resource
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get", "list", "watch", "update", "patch"]
# Gateway API status subresources - write status
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gatewayclasses/status", "gateways/status", "httproutes/status", "grpcroutes/status", "backendtlspolicies/status", "listenersets/status"]
verbs: ["get", "update", "patch"]
# ServiceImport - a backendRef may target an imported multicluster Service
- apiGroups: ["multicluster.x-k8s.io"]
resources: ["serviceimports"]
verbs: ["get", "list", "watch"]
# CustomResourceDefinitions - single Get of the gatewayclasses CRD to read
# the bundle-version annotation for the SupportedVersion condition
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get"]
# Core API - read only
- apiGroups: [""]
resources: ["services", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["get", "list", "watch"]
# EndpointSlice - the proxy endpoint reconciler discovers proxy pods so a
# newly-joined replica gets the cached config pushed immediately
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch"]
# Events - route reconcilers emit Events via both the core (v1) and the new
# (events.k8s.io/v1) recorders; grant both so neither path is denied
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["events.k8s.io"]
resources: ["events"]
verbs: ["create", "patch"]
# Deployments - the proxy Secret reconciler patches the proxy Deployment's
# pod-template annotation to roll pods when the tunnel-token Secret rotates;
# patch only, no create/delete (the chart owns Deployment lifecycle)
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch"]
# GatewayClassConfig CRD
- apiGroups: ["cf.k8s.lex.la"]
resources: ["gatewayclassconfigs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["cf.k8s.lex.la"]
resources: ["gatewayclassconfigs/status"]
verbs: ["get", "update", "patch"]
# ExternalBackend CRD - a backendRef may target an out-of-cluster endpoint
- apiGroups: ["cf.k8s.lex.la"]
resources: ["externalbackends"]
verbs: ["get", "list", "watch"]
# Leader election
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
v3 RBAC scope
The v3 controller reads Secrets and ConfigMaps and writes only status subresources; it does not create or delete workloads. The one workload mutation it makes is patching the proxy Deployment's pod-template annotation when the tunnel-token Secret rotates (to trigger a native rolling restart) — it does not manage Deployment lifecycle. The v2 chart additionally granted cluster-wide write on Pods, ReplicaSets and ServiceAccounts for the Helm-SDK code path that managed cloudflared; those rules were removed when that code path was deleted.
Container Security¶
The controller container follows security best practices:
| Setting | Value | Rationale |
|---|---|---|
runAsNonRoot | true | Never run as root |
runAsUser | 65534 | nobody user |
readOnlyRootFilesystem | true | Prevent filesystem modifications |
allowPrivilegeEscalation | false | Prevent privilege escalation |
capabilities.drop | ALL | Drop all Linux capabilities |
seccompProfile.type | RuntimeDefault | Use default seccomp profile |
Network Security¶
Egress Requirements¶
The controller only needs egress to:
| Destination | Port | Purpose |
|---|---|---|
api.cloudflare.com | 443 | Cloudflare API |
| Kubernetes API | 443/6443 | Watch resources |
NetworkPolicy Example¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: cloudflare-tunnel-gateway-controller
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: cloudflare-tunnel-gateway-controller
policyTypes:
- Ingress
- Egress
ingress:
# Prometheus scraping
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- port: 8080
egress:
# Kubernetes API and Cloudflare API
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- port: 443
- port: 6443
# DNS
- to: []
ports:
- port: 53
protocol: UDP
Supply Chain Security¶
Container Image Verification¶
Container images are signed with cosign (keyless):
cosign verify ghcr.io/lexfrei/cloudflare-tunnel-gateway-controller:latest \
--certificate-identity-regexp="https://github.com/lexfrei/cloudflare-tunnel-gateway-controller" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
Helm Chart Verification¶
Secrets in Logs¶
The controller is designed to never log sensitive information:
- API tokens are not logged
- Tunnel tokens are not logged
- Secret contents are not logged
Report Log Leaks
If you find sensitive data in logs, please report it as a security issue.
Security Scanning¶
The project uses automated security scanning:
| Tool | Purpose |
|---|---|
| Trivy | Vulnerability scanning in CI |
| gosec | Go security linter |
| Dependabot/Renovate | Dependency updates |
Incident Response¶
If you believe the controller has been compromised:
- Revoke Cloudflare API token immediately
- Delete the controller deployment
- Review Cloudflare audit logs for unauthorized changes
- Rotate tunnel credentials if needed
- Report the incident via security email
Secure Deployment Checklist¶
- API token stored in Kubernetes Secret (not in values.yaml)
- API token has minimal required permissions
- Controller running as non-root
- Read-only root filesystem enabled
- NetworkPolicy restricting egress
- ServiceAccount with minimal RBAC
- Container image verified with cosign
- Prometheus monitoring enabled
- Alerts configured for anomalous behavior