Remote Access¶
The fc-data Supabase instance is not exposed on the public internet. Two hostnames front the same tunnel:
| Hostname | CF Access gate | Credential | Scope |
|---|---|---|---|
db.formulacode.org |
yes, service-token policy | service-role key | full read/write |
api.formulacode.org |
no | anon (publishable) key | SELECT on four tables |
Pipeline operators use db.*; public websites use api.*. Two independent
credentials (service token + service-role key) must both leak before full
write access is at risk.
Architecture¶
flowchart LR
User["Pipeline operator<br/>(service-role key + CF token)"]
Web["Public website<br/>(anon key)"]
CFE1["db.formulacode.org"]
CFE2["api.formulacode.org"]
CFA["Cloudflare Access<br/>(service-token policy)"]
subgraph Host["Host machine"]
CFD["cloudflared<br/>(datasmith-db tunnel)"]
SB["Supabase :54321<br/>PostgREST + RLS"]
end
User --> CFE1 --> CFA --> CFD
Web --> CFE2 --> CFD
CFD --> SB
On db.*, Cloudflare Access rejects any request without a valid service
token before it reaches Supabase. On api.*, there is no Access application;
Supabase's anon role has SELECT grants on four tables only (see
supabase/migrations/00012_public_read_rls.sql and
supabase/migrations/00015_revoke_anon_select.sql), so anything else returns
permission denied.
For users¶
Full read/write access¶
You need two things in tokens.env:
SUPABASE_URL=https://db.formulacode.org
SUPABASE_KEY=<service-role key>
DATASMITH_CF_ACCESS_CLIENT_ID=<client id>
DATASMITH_CF_ACCESS_CLIENT_SECRET=<client secret>
Both the service-role key and the Cloudflare Access credentials are issued by a maintainer. To request them, open an issue asking for remote access; include the machine or project you need the credentials for.
Verify the connection:
Public read-only access¶
Use the Supabase anon key (shown as the "Publishable" key in
supabase status) against https://api.formulacode.org. No Cloudflare
Access credentials are required for this hostname; the anon key is the only
auth layer.
| Table | Exposed |
|---|---|
repositories |
Repository metadata |
pull_requests |
PR metadata, classification, patches |
candidate_containers |
Successful build scripts per SHA |
harbor_runs |
Benchmark speedup results |
Example:
const SUPABASE_URL = "https://api.formulacode.org";
const ANON_KEY = "sb_publishable_...";
const res = await fetch(
`${SUPABASE_URL}/rest/v1/repositories?select=owner,repo,stars&order=stars.desc&limit=20`,
{
headers: {
"apikey": ANON_KEY,
"Authorization": `Bearer ${ANON_KEY}`,
},
},
);
Writes are rejected by RLS (HTTP 401, new row violates row-level security
policy). Reads of any table outside the four listed above return
permission denied for table <name>.
For developers (host machine setup)¶
This section covers standing up the tunnel and Access policy. Day-to-day operation is in the Makefile targets at the bottom.
1. Install cloudflared¶
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb \
-o cloudflared.deb
sudo dpkg -i cloudflared.deb
cloudflared --version
2. Authenticate and create the tunnel¶
Note the printed Tunnel ID. Credentials are saved to
~/.cloudflared/<TUNNEL_ID>.json.
3. Configure the tunnel¶
~/.cloudflared/config.yml:
tunnel: <TUNNEL_ID>
credentials-file: /home/<user>/.cloudflared/<TUNNEL_ID>.json
ingress:
- hostname: db.formulacode.org
service: http://localhost:54321
- hostname: api.formulacode.org
service: http://localhost:54321
- service: http_status:404
Both hostnames proxy to the same Supabase port. db.* is the gated
read/write path; api.* is the ungated public-read path. The distinction
lives in Cloudflare Access (step 6), not in the tunnel config.
4. DNS records¶
cloudflared tunnel route dns datasmith-db db.formulacode.org
cloudflared --config ~/.cloudflared/config-db.yml tunnel route dns \
--overwrite-dns datasmith-db api.formulacode.org
The --config + --overwrite-dns flags on the second command are necessary
because cloudflared resolves the tunnel name against the default config;
without them it will route the CNAME to the wrong tunnel.
5. Run the tunnel¶
make db-tunnel # foreground
sudo cloudflared service install # or as a systemd service
sudo systemctl enable --now cloudflared
At this point https://db.formulacode.org proxies to local Supabase but
Cloudflare Access blocks everything until the policy is in place.
6. Cloudflare Access policy — for db.* only¶
In Cloudflare Zero Trust → Access → Applications, add a self-hosted app:
- Application name:
datasmith-db - Application domain:
db.formulacode.org - Session duration: 24 hours
- Policy: action
Service Auth, include the service token below.
Then under Access → Service Auth → Service Tokens, create a token (e.g.
datasmith-remote) and copy both the Client ID and Client Secret. The secret
is only shown once. Hand these to the requesting user along with the
service-role key.
Do not create an Access application for api.formulacode.org. That
hostname must remain ungated so anon-key clients can reach PostgREST. The
anon key plus RLS grants are the only auth layer there.
7. Apply the Supabase migrations¶
The anon-key path depends on two migrations being applied against the host Supabase instance:
docker exec -i supabase_db_<project> psql -U postgres -d postgres \
< supabase/migrations/00012_public_read_rls.sql
docker exec -i supabase_db_<project> psql -U postgres -d postgres \
< supabase/migrations/00015_revoke_anon_select.sql
00012 enables RLS and adds the public_read SELECT policy on the four
exposed tables. 00015 revokes the default anon SELECT ON ALL TABLES
grant that Supabase ships with, then re-grants it only on those four tables
and sets the default for future tables to anon-invisible. Without 00015,
anon can read every table because Supabase's default grants override the
absence of RLS.
How the client picks up the headers¶
When both DATASMITH_CF_ACCESS_CLIENT_ID and
DATASMITH_CF_ACCESS_CLIENT_SECRET are set, datasmith.utils.db injects the
CF-Access-Client-Id and CF-Access-Client-Secret headers into every
Supabase client request via ClientOptions. When unset, no extra headers are
added and behavior is identical to local development.
Troubleshooting¶
| Symptom | Likely cause |
|---|---|
403 Forbidden |
Missing or invalid CF Access headers |
502 Bad Gateway |
cloudflared not running, or Supabase is down |
Connection refused |
DNS not resolving; check the CNAME was created |