# Cloudflare

- [Cloudflare](#cloudflare)
  - [Argo Tunnel](#argo-tunnel)
    - [Prerequisites](#prerequisites)
    - [Setup](#setup)
  - [Deploy tunnel for app (**START HERE IF TUNNEL ALREADY DEPLOYED**)](#deploy-tunnel-for-app-start-here-if-tunnel-already-deployed)
  - [Secrets & substitution](#secrets--substitution)


# Argo Tunnel

Cloudflare Tunnel provides you with a secure way to connect your resources to Cloudflare without a publicly routable IP address. With Tunnel, you do not send traffic to an external IP -- instead, a lightweight daemon in your infrastructure (cloudflared) creates outbound-only connections to Cloudflare's edge.


## Prerequisites

Install the [cloudflared CLI](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-local-tunnel/#1-download-and-install-cloudflared)


## Setup

[github example docs](https://github.com/cloudflare/argo-tunnel-examples/tree/master/named-tunnel-k8s) [documentation](https://developers.cloudflare.com/cloudflare-one/tutorials/many-cfd-one-tunnel#deploy-cloudflared) [config](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/local-management/configuration-file/)

1.  If you haven't, login to you Cloudflare account to obtain a certificate.

    ``` sh
    cloudflared tunnel login
    ```

    > This saves a cert.pem file to `${HOME}/.cloudflared/cert.pem`

2.  Create a tunnel, change example-tunnel to the name you want to assign to your tunnel.

    ``` sh
    # list existing
    cloudflared tunnel list
    # create new
    cloudflared tunnel create k8s-argo-tunnel
    ```

    > This writes a tunnel credential files to `${HOME}/.cloudflared/{GUID}.json` and prints a `Tunnel Token` to the terminal

3.  Extract the secret value and send to `.envrc` for use in secret template

    ``` sh
    GUID=... # from above step
    SECRET_CLOUDFLARE_TUNNEL_CREDS=$(kubectl create secret generic tunnel-credentials \
    --from-file=credentials.json="${HOME}/.cloudflared/${GUID}.json" \
    --output=yaml \
    --dry-run=client | grep credentials.json | awk '{ print $2 }')
    echo "export SECRET_CLOUDFLARE_TUNNEL_CREDS=\"$SECRET_CLOUDFLARE_TUNNEL_CREDS\"" >> .envrc
    ```

4.  Substitute and encrypt the secret

    ``` sh
    # substitute
    envsubst < ./path/to/cloudflared/secret.sops.yaml.tmpl >! ./path/to/cloudflared/secret.sops.yaml

    # encrypt
    sops --encrypt --in-place ./path/to/cloudflared/secret.sops.yaml
    ```


# Deploy tunnel for app (**START HERE IF TUNNEL ALREADY DEPLOYED**)

1.  Associate your Tunnel with a DNS record.

    Via CLI:

    ``` sh
    cloudflared tunnel route dns k8s-argo-tunnel "<SUBDOMAIN>.${SECRET_DOMAIN}"
    ```

    Via WebUI: Log into Cloudflare portal and create a CNAME record for `<SUBDOMAIN>` with target `<TUNNEL_ID>.cfargotunnel.com`

    > Repeat this process for all (sub)domains to be proxied over Cloudflared Tunnel

2.  Deploy cloudflared by applying its manifest (managed by Flux kustomization).

    When Cloudflare receives traffic for the DNS or Load Balancing hostname you configured in the previous step, it will send that traffic to the cloudflareds running in this deployment. Those cloudflared instances will proxy the request to your app's Service.


# Secrets & substitution

`cloudflared` pulls its identity from three distinct sources -- do not conflate them:

| Name | Origin | Provides | Consumed by |
|----|----|----|----|
| `cloudflared-tunnel` | bootstrap secret (SOPS, `kubernetes/bootstrap/`) | `TUNNEL_ID` | pod env `TUNNEL_ID`, **and** Flux `${TUNNEL_ID}` build-time substitution (DNSEndpoint) |
| `cloudflared-credential` | ExternalSecret → 1Password (`network.cloudflared`) | `credentials.json` | pod volume mounted at `/etc/cloudflared/creds/credentials.json` |
| `cluster-secrets` | ClusterExternalSecret → 1Password | `SECRET_DOMAIN`, etc. | Flux `${SECRET_DOMAIN}` substitution (config, HTTPRoutes) |

`credentials.json` is rendered by ExternalSecrets from 1Password and is **no longer stored as a SOPS secret** in-repo. The legacy `secret.sops.yaml*` flow above is kept for historical reference.


## `${TUNNEL_ID}` substitution (important)

[`dnsendpoint.yaml`](./app/dnsendpoint.yaml) builds the tunnel CNAME `${TUNNEL_ID}.cfargotunnel.com` via Flux `postBuild` substitution. This app's `ks.yaml` intentionally does **not** set `postBuild.substituteFrom` -- the parent `flux-cluster` Kustomization owns that field (see gotcha \#4 in the repo-root `AGENTS.md`). `cloudflared-tunnel` is merged in via a targeted JSON6902 append in [`kubernetes/flux/cluster/ks.yaml`](../../../flux/cluster/ks.yaml), yielding `[cluster-secrets, cloudflared-tunnel]`.

If `${TUNNEL_ID}` fails to resolve (strict-mode `variable not set`, blocking this Kustomization): confirm the `cloudflared-tunnel` secret exists in `network` with a `TUNNEL_ID` key and the parent's append patch is present, then reconcile the **parent** -- `flux reconcile kustomization flux-cluster --with-source`.

> **Note**
>
> Source: [`kubernetes/apps/network/cloudflared/README.md`](https://github.com/ahgraber/homelab-gitops-k3s/blob/main/kubernetes/apps/network/cloudflared/README.md)
