chute

Architecture

Traffic Flow

Internet → Cloudflare Edge → Tunnel → cloudflared pod → K8s Service
                ↑                          (runs in-cluster)
        Access gate (email OTP)

When a user visits the exposed hostname:

  1. The request hits Cloudflare’s edge network
  2. Cloudflare Access checks for a valid session — if none, the user is prompted for email OTP
  3. Once authenticated, the request is forwarded through the Cloudflare Tunnel
  4. The cloudflared pod running in the cluster receives the request
  5. cloudflared forwards it to the target Kubernetes Service via svc.cluster.local

CRD Model

The operator defines two custom resources in the chutes.troubleshat.com API group:

ChuteConfig (cluster-scoped)
Holds Cloudflare credentials — a reference to a namespaced Secret containing the API token, plus account ID, zone ID, and base domain. One config can be shared across namespaces. The controller validates credentials by ensuring the OTP identity provider exists on the Cloudflare account.
ChuteInstance (namespace-scoped)
References a ChuteConfig by name and a local Kubernetes Service. Declares a hostname and access policy (allowed emails/domains). The controller orchestrates the full Cloudflare resource chain.

Reconciliation

ChuteConfigReconciler

  1. Reads the API token from the referenced Secret
  2. Calls Cloudflare to ensure an OTP identity provider exists
  3. Stores the OTP IdP ID in status.otpIdentityProviderId
  4. Sets status.ready = true

ChuteInstanceReconciler

Executes a step-by-step creation sequence. Each Cloudflare resource ID is stored in status so reconciliation is idempotent and resumable — if the operator restarts mid-reconciliation, it picks up where it left off.

Step Action Status Field
1 Create Cloudflare Tunnel (named chute-{namespace}-{name}) status.tunnelId
2 PUT tunnel ingress config (hostname → svc.cluster.local mapping)
3 Create CNAME DNS record ({tunnelId}.cfargotunnel.com) status.dnsRecordId
4 Create Access Application (self-hosted, OTP only) status.accessAppId
5 Create Access Policy (email/domain allow rules) status.accessPolicyId
6 Create K8s Secret with tunnel token + cloudflared Deployment

The cloudflared Deployment runs with:

Deletion

The operator uses a finalizer (chutes.troubleshat.com/cleanup) to ensure Cloudflare resources are cleaned up before the Kubernetes object is removed.

Deletion proceeds in reverse order:

  1. Delete Access Policy
  2. Delete Access Application
  3. Delete DNS Record
  4. Delete Tunnel
  5. Remove finalizer (allows Kubernetes to delete the object)

The cloudflared Deployment and tunnel token Secret are garbage-collected automatically via owner references.

Cloudflare API Client

The internal/cloudflare/ package is a thin HTTP client wrapping the Cloudflare v4 REST API. It covers:

All methods accept context.Context for cancellation. The standard Cloudflare response envelope ({success, errors, result}) is handled in a shared do() method.

All Ensure* methods are find-or-create: they first check if the resource already exists (by name or domain), returning it if found, or creating it otherwise. This makes reconciliation safe to retry.

Naming Conventions

Resource Name Pattern
Cloudflare Tunnel chute-{namespace}-{instance}
Access Application chute-{namespace}-{instance}
Access Policy chute-operator-allow
K8s tunnel token Secret chute-{instance}-tunnel-token
K8s cloudflared Deployment chute-{instance}-cloudflared