From b87dc29706f5d86377e509d857d8f4a44ee79612 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 6 Oct 2026 02:33:32 +0200 Subject: [PATCH] supabase: keep logflare's API key out of its log (hq issue 268) vector handed logflare its API key as ?api_key= in every sink URL, and logflare 1.4.0 prints a failed request's whole URL in its Plug.Cowboy error report. Its ingest fails on every request here, so the key was in the analytics log about every ten seconds. The report is an error, so no log level hides it. Every sink now sends the key in the x-api-key header, which logflare reads first. A start script refuses to start logflare while the vector config it is given still puts the key in a URL. The key is marked "applied", not "at-start": logflare writes it into its default user once and never updates it, so the mesh must not rotate it by restarting. --- modules/supabase/module.json | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/modules/supabase/module.json b/modules/supabase/module.json index 7d613cd..c578306 100644 --- a/modules/supabase/module.json +++ b/modules/supabase/module.json @@ -37,7 +37,10 @@ "service-role-key": "${dir:state}/service-role-key.secret", "dashboard-user": "${dir:state}/dashboard-user.secret", "dashboard": "${dir:state}/dashboard.secret", - "logflare": "${dir:state}/logflare.secret", + "logflare": { + "path": "${dir:state}/logflare.secret", + "taken": "applied" + }, "pooler-vault": "${dir:state}/pooler-vault.secret", "key-base-a": "${dir:state}/key-base-a.secret", "key-base-b": "${dir:state}/key-base-b.secret", @@ -130,7 +133,7 @@ "type": "file", "path": "${dir:state}/vector.yml", "mode": "0600", - "content": "# Written by the mesh (modules/supabase).\napi:\n enabled: true\n address: 0.0.0.0:9001\n\nsources:\n docker_host:\n type: docker_logs\n include_containers:\n - supabase-kong\n - supabase-auth\n - supabase-rest\n - realtime-dev.supabase-realtime\n - supabase-storage\n - supabase-edge-functions\n - supabase-db\n\ntransforms:\n project_logs:\n type: remap\n inputs:\n - docker_host\n source: |-\n .project = \"default\"\n .event_message = del(.message)\n .appname = del(.container_name)\n del(.container_created_at)\n del(.container_id)\n del(.source_type)\n del(.stream)\n del(.label)\n del(.image)\n del(.host)\n del(.stream)\n router:\n type: route\n inputs:\n - project_logs\n route:\n kong: '.appname == \"supabase-kong\"'\n auth: '.appname == \"supabase-auth\"'\n rest: '.appname == \"supabase-rest\"'\n realtime: '.appname == \"realtime-dev.supabase-realtime\"'\n storage: '.appname == \"supabase-storage\"'\n functions: '.appname == \"supabase-edge-functions\"'\n db: '.appname == \"supabase-db\"'\n # Ignores non nginx errors since they are related with kong booting up\n kong_logs:\n type: remap\n inputs:\n - router.kong\n source: |-\n req, err = parse_nginx_log(.event_message, \"combined\")\n if err == null {\n .timestamp = req.timestamp\n .metadata.request.headers.referer = req.referer\n .metadata.request.headers.user_agent = req.agent\n .metadata.request.headers.cf_connecting_ip = req.client\n .metadata.request.method = req.method\n .metadata.request.path = req.path\n .metadata.request.protocol = req.protocol\n .metadata.response.status_code = req.status\n }\n if err != null {\n abort\n }\n # Ignores non nginx errors since they are related with kong booting up\n kong_err:\n type: remap\n inputs:\n - router.kong\n source: |-\n .metadata.request.method = \"GET\"\n .metadata.response.status_code = 200\n parsed, err = parse_nginx_log(.event_message, \"error\")\n if err == null {\n .timestamp = parsed.timestamp\n .severity = parsed.severity\n .metadata.request.host = parsed.host\n .metadata.request.headers.cf_connecting_ip = parsed.client\n url, err = split(parsed.request, \" \")\n if err == null {\n .metadata.request.method = url[0]\n .metadata.request.path = url[1]\n .metadata.request.protocol = url[2]\n }\n }\n if err != null {\n abort\n }\n # Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.\n auth_logs:\n type: remap\n inputs:\n - router.auth\n source: |-\n parsed, err = parse_json(.event_message)\n if err == null {\n .metadata.timestamp = parsed.time\n .metadata = merge!(.metadata, parsed)\n }\n # PostgREST logs are structured so we separate timestamp from message using regex\n rest_logs:\n type: remap\n inputs:\n - router.rest\n source: |-\n parsed, err = parse_regex(.event_message, r'^(?P