From b38285beb9591edf25966f5234beb8e9076e9ac0 Mon Sep 17 00:00:00 2001 From: fchinembiri Date: Tue, 19 May 2026 17:03:33 +0200 Subject: [PATCH] feat(k8s): add supabase for basket flutter app --- k8s/base/kustomization.yaml | 2 + k8s/base/supabase-basket-namespace.yaml | 4 + k8s/base/supabase-basket.yaml | 3266 +++++++++++++++++++++++ 3 files changed, 3272 insertions(+) create mode 100644 k8s/base/supabase-basket-namespace.yaml create mode 100644 k8s/base/supabase-basket.yaml diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml index 033a5ba..b0ad424 100644 --- a/k8s/base/kustomization.yaml +++ b/k8s/base/kustomization.yaml @@ -20,6 +20,8 @@ resources: - geocrop-tiler-rewrite.yaml - 60-ingress-minio.yaml - ntfy.yaml +- supabase-basket-namespace.yaml +- supabase-basket.yaml images: - name: frankchine/geocrop-api newName: frankchine/geocrop-api diff --git a/k8s/base/supabase-basket-namespace.yaml b/k8s/base/supabase-basket-namespace.yaml new file mode 100644 index 0000000..73bb86c --- /dev/null +++ b/k8s/base/supabase-basket-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: supabase-basket diff --git a/k8s/base/supabase-basket.yaml b/k8s/base/supabase-basket.yaml new file mode 100644 index 0000000..c107548 --- /dev/null +++ b/k8s/base/supabase-basket.yaml @@ -0,0 +1,3266 @@ +--- +# Source: supabase/templates/analytics/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-analytics + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/auth/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-auth + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/db/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-db + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/functions/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-functions + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/imgproxy/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-imgproxy + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/kong/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-kong + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/meta/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-meta + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/realtime/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-realtime + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/rest/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-rest + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/storage/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-storage + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/studio/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-studio + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/vector/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: supabase-basket-supabase-vector + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +--- +# Source: supabase/templates/secret/analytics.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-analytics + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + publicAccessToken: "eW91ci1zdXBlci1zZWNyZXQtYW5kLWxvbmctbG9nZmxhcmUta2V5LXB1YmxpYw==" + privateAccessToken: "eW91ci1zdXBlci1zZWNyZXQtYW5kLWxvbmctbG9nZmxhcmUta2V5LXByaXZhdGU=" +--- +# Source: supabase/templates/secret/apikey.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-apikey + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + publishableKey: "" + secretKey: "" + anonKeyAsymmetric: "" + serviceRoleKeyAsymmetric: "" + jwtKeys: "" + jwtJwks: "" +--- +# Source: supabase/templates/secret/dashboard.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-dashboard + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + username: "c3VwYWJhc2U=" + password: "dGhpc19wYXNzd29yZF9pc19pbnNlY3VyZV9hbmRfc2hvdWxkX2JlX3VwZGF0ZWQ=" + openAiApiKey: "a2V5X3N1cGVyX3NlY3JldA==" +--- +# Source: supabase/templates/secret/db.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-db + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + password: "eW91ci1zdXBlci1zZWNyZXQtYW5kLWxvbmctcG9zdGdyZXMtcGFzc3dvcmQ=" + database: "cG9zdGdyZXM=" + password_encoded: "eW91ci1zdXBlci1zZWNyZXQtYW5kLWxvbmctcG9zdGdyZXMtcGFzc3dvcmQ=" +--- +# Source: supabase/templates/secret/jwt.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-jwt + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + anonKey: "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5QWdDaUFnSUNBaWNtOXNaU0k2SUNKaGJtOXVJaXdLSUNBZ0lDSnBjM01pT2lBaWMzVndZV0poYzJVdFpHVnRieUlzQ2lBZ0lDQWlhV0YwSWpvZ01UWTBNVGMyT1RJd01Dd0tJQ0FnSUNKbGVIQWlPaUF4TnprNU5UTTFOakF3Q24wLmRjX1g1aVJfVlBfcVQwenNpeWpfSV9PWjJUOUZ0UlUyQkJOV044QnU0R0U=" + serviceKey: "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5QWdDaUFnSUNBaWNtOXNaU0k2SUNKelpYSjJhV05sWDNKdmJHVWlMQW9nSUNBZ0ltbHpjeUk2SUNKemRYQmhZbUZ6WlMxa1pXMXZJaXdLSUNBZ0lDSnBZWFFpT2lBeE5qUXhOelk1TWpBd0xBb2dJQ0FnSW1WNGNDSTZJREUzT1RrMU16VTJNREFLZlEuRGFZbE5Fb1VyckVuMklnN3RxaWJTLVBISzV2Z3VzYmNibzdYMzZYVnQ0UQ==" + secret: "eW91ci1zdXBlci1zZWNyZXQtand0LXRva2VuLXdpdGgtYXQtbGVhc3QtMzItY2hhcmFjdGVycy1sb25n" +--- +# Source: supabase/templates/secret/meta.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-meta + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + cryptoKey: "eW91ci1lbmNyeXB0aW9uLWtleS0zMi1jaGFycy1taW4=" +--- +# Source: supabase/templates/secret/minio.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-minio + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + user: "c3VwYS1zdG9yYWdl" + password: "c2VjcmV0MTIzNA==" +--- +# Source: supabase/templates/secret/realtime.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-realtime + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + secretKeyBase: "VXBOVm50bjNjRHhISnBxOTlZTWMxVDFBUWdRcGM4a2ZZVHVSZ0JpWWExNUJMcng4ZXRRb1h6M2dadjEvdTJvcQ==" +--- +# Source: supabase/templates/secret/s3.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-s3 + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + keyId: "NjI1NzI5YTA4Yjk1YmYxYjdmZjM1MWE2NjNmM2EyM2M=" + accessKey: "ODUwMTgxZTQ2NTJkZDAyM2I3YTk4YzU4YWUwZDJkMzRiZDQ4N2VlMGNjMzI1NGFlZDZlZGEzNzMwNzQyNTkwNw==" +--- +# Source: supabase/templates/secret/smtp.yaml +apiVersion: v1 +kind: Secret +metadata: + name: supabase-basket-smtp + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + username: "ZmFrZV9tYWlsX3VzZXI=" + password: "ZmFrZV9tYWlsX3Bhc3N3b3Jk" +--- +# Source: supabase/templates/db/initdb.config.yaml +kind: ConfigMap +apiVersion: v1 +metadata: + name: supabase-basket-supabase-db-initdb + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +data: + 99-jwt.sql: | + \set jwt_secret `echo "$JWT_SECRET"` + \set jwt_exp `echo "$JWT_EXP"` + + ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret'; + ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp'; + + 99-pooler.sql: | + \set pguser `echo "$POSTGRES_USER"` + + \c _supabase + create schema if not exists _supavisor; + alter schema _supavisor owner to :pguser; + \c postgres + + 99-logs.sql: | + \set pguser `echo "$POSTGRES_USER"` + \c _supabase + create schema if not exists _analytics; + alter schema _analytics owner to :pguser; + \c postgres + + 99-realtime.sql: | + \set pguser `echo "$POSTGRES_USER"` + + create schema if not exists _realtime; + alter schema _realtime owner to :pguser; + + 99-roles.sql: | + -- NOTE: change to your own passwords for production environments + \set pgpass `echo "$POSTGRES_PASSWORD"` + + ALTER USER authenticator WITH PASSWORD :'pgpass'; + ALTER USER pgbouncer WITH PASSWORD :'pgpass'; + ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass'; + ALTER USER supabase_functions_admin WITH PASSWORD :'pgpass'; + ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass'; + + 97-_supabase.sql: | + \set pguser `echo "$POSTGRES_USER"` + + CREATE DATABASE _supabase WITH OWNER :pguser; + + 98-webhooks.sql: | + BEGIN; + -- Create pg_net extension + CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions; + -- Create supabase_functions schema + CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin; + GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role; + ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role; + ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role; + ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role; + -- supabase_functions.migrations definition + CREATE TABLE supabase_functions.migrations ( + version text PRIMARY KEY, + inserted_at timestamptz NOT NULL DEFAULT NOW() + ); + -- Initial supabase_functions migration + INSERT INTO supabase_functions.migrations (version) VALUES ('initial'); + -- supabase_functions.hooks definition + CREATE TABLE supabase_functions.hooks ( + id bigserial PRIMARY KEY, + hook_table_id integer NOT NULL, + hook_name text NOT NULL, + created_at timestamptz NOT NULL DEFAULT NOW(), + request_id bigint + ); + CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id); + CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name); + COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.'; + CREATE FUNCTION supabase_functions.http_request() + RETURNS trigger + LANGUAGE plpgsql + AS $function$ + DECLARE + request_id bigint; + payload jsonb; + url text := TG_ARGV[0]::text; + method text := TG_ARGV[1]::text; + headers jsonb DEFAULT '{}'::jsonb; + params jsonb DEFAULT '{}'::jsonb; + timeout_ms integer DEFAULT 1000; + BEGIN + IF url IS NULL OR url = 'null' THEN + RAISE EXCEPTION 'url argument is missing'; + END IF; + + IF method IS NULL OR method = 'null' THEN + RAISE EXCEPTION 'method argument is missing'; + END IF; + + IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN + headers = '{"Content-Type": "application/json"}'::jsonb; + ELSE + headers = TG_ARGV[2]::jsonb; + END IF; + + IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN + params = '{}'::jsonb; + ELSE + params = TG_ARGV[3]::jsonb; + END IF; + + IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN + timeout_ms = 1000; + ELSE + timeout_ms = TG_ARGV[4]::integer; + END IF; + + CASE + WHEN method = 'GET' THEN + SELECT http_get INTO request_id FROM net.http_get( + url, + params, + headers, + timeout_ms + ); + WHEN method = 'POST' THEN + payload = jsonb_build_object( + 'old_record', OLD, + 'record', NEW, + 'type', TG_OP, + 'table', TG_TABLE_NAME, + 'schema', TG_TABLE_SCHEMA + ); + + SELECT http_post INTO request_id FROM net.http_post( + url, + payload, + params, + headers, + timeout_ms + ); + ELSE + RAISE EXCEPTION 'method argument % is invalid', method; + END CASE; + + INSERT INTO supabase_functions.hooks + (hook_table_id, hook_name, request_id) + VALUES + (TG_RELID, TG_NAME, request_id); + + RETURN NEW; + END + $function$; + -- Supabase super admin + DO + $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_roles + WHERE rolname = 'supabase_functions_admin' + ) + THEN + CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION; + END IF; + END + $$; + GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin; + GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin; + GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin; + ALTER USER supabase_functions_admin SET search_path = "supabase_functions"; + ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin; + ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin; + ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin; + GRANT supabase_functions_admin TO postgres; + -- Remove unused supabase_pg_net_admin role + DO + $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_roles + WHERE rolname = 'supabase_pg_net_admin' + ) + THEN + REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin; + DROP OWNED BY supabase_pg_net_admin; + DROP ROLE supabase_pg_net_admin; + END IF; + END + $$; + -- pg_net grants when extension is already enabled + DO + $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_extension + WHERE extname = 'pg_net' + ) + THEN + GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + END IF; + END + $$; + -- Event trigger for pg_net + CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access() + RETURNS event_trigger + LANGUAGE plpgsql + AS $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_event_trigger_ddl_commands() AS ev + JOIN pg_extension AS ext + ON ev.objid = ext.oid + WHERE ext.extname = 'pg_net' + ) + THEN + GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + END IF; + END; + $$; + COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net'; + DO + $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_event_trigger + WHERE evtname = 'issue_pg_net_access' + ) THEN + CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION') + EXECUTE PROCEDURE extensions.grant_pg_net_access(); + END IF; + END + $$; + INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants'); + ALTER function supabase_functions.http_request() SECURITY DEFINER; + ALTER function supabase_functions.http_request() SET search_path = supabase_functions; + REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC; + GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role; + COMMIT; +--- +# Source: supabase/templates/db/migration.config.yaml +kind: ConfigMap +apiVersion: v1 +metadata: + name: supabase-basket-supabase-db-migrations + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +data: + null +--- +# Source: supabase/templates/functions/functions.config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: supabase-basket-supabase-functions-main + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +data: + index.ts: | + import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts' + + console.log('main function started') + + const JWT_SECRET = Deno.env.get('JWT_SECRET') + const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true' + + function getAuthToken(req: Request) { + const authHeader = req.headers.get('authorization') + if (!authHeader) { + throw new Error('Missing authorization header') + } + const [bearer, token] = authHeader.split(' ') + if (bearer !== 'Bearer') { + throw new Error(`Auth header is not 'Bearer {token}'`) + } + return token + } + + async function verifyJWT(jwt: string): Promise { + const encoder = new TextEncoder() + const secretKey = encoder.encode(JWT_SECRET) + try { + await jose.jwtVerify(jwt, secretKey) + } catch (err) { + console.error(err) + return false + } + return true + } + + Deno.serve(async (req: Request) => { + if (req.method !== 'OPTIONS' && VERIFY_JWT) { + try { + const token = getAuthToken(req) + const isValidJWT = await verifyJWT(token) + + if (!isValidJWT) { + return new Response(JSON.stringify({ msg: 'Invalid JWT' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }) + } + } catch (e) { + console.error(e) + return new Response(JSON.stringify({ msg: e.toString() }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }) + } + } + + const url = new URL(req.url) + const { pathname } = url + const path_parts = pathname.split('/') + const service_name = path_parts[1] + + if (!service_name || service_name === '') { + const error = { msg: 'missing function name in request' } + return new Response(JSON.stringify(error), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) + } + + const servicePath = `/home/deno/functions/${service_name}` + console.error(`serving the request with ${servicePath}`) + + const memoryLimitMb = 150 + const workerTimeoutMs = 1 * 60 * 1000 + const noModuleCache = false + const importMapPath = null + const envVarsObj = Deno.env.toObject() + const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]]) + + try { + const worker = await EdgeRuntime.userWorkers.create({ + servicePath, + memoryLimitMb, + workerTimeoutMs, + noModuleCache, + importMapPath, + envVars, + }) + return await worker.fetch(req) + } catch (e) { + const error = { msg: e.toString() } + return new Response(JSON.stringify(error), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }) + } + }) +--- +# Source: supabase/templates/kong/config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: supabase-basket-supabase-kong + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +data: + kong-entrypoint.sh: | + #!/bin/bash + + set -euo pipefail + + if [ -n "${SUPABASE_SECRET_KEY:-}" ] && [ -n "${SUPABASE_PUBLISHABLE_KEY:-}" ]; then + export LUA_AUTH_EXPR="\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or (headers.apikey == '${SUPABASE_SECRET_KEY}' and 'Bearer ${SERVICE_ROLE_KEY_ASYMMETRIC}') or (headers.apikey == '${SUPABASE_PUBLISHABLE_KEY}' and 'Bearer ${ANON_KEY_ASYMMETRIC}') or headers.apikey)" + export LUA_RT_WS_EXPR="\$((query_params.apikey == '${SUPABASE_SECRET_KEY}' and '${SERVICE_ROLE_KEY_ASYMMETRIC}') or (query_params.apikey == '${SUPABASE_PUBLISHABLE_KEY}' and '${ANON_KEY_ASYMMETRIC}') or query_params.apikey)" + else + export LUA_AUTH_EXPR="\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or headers.apikey)" + export LUA_RT_WS_EXPR="\$(query_params.apikey)" + fi + + echo "Replacing env placeholders of /usr/local/kong/kong.yml" + + sed \ + -e "s|\${SUPABASE_ANON_KEY}|${SUPABASE_ANON_KEY}|" \ + -e "s|\${SUPABASE_SERVICE_KEY}|${SUPABASE_SERVICE_KEY}|" \ + -e "s|\${SUPABASE_PUBLISHABLE_KEY}|${SUPABASE_PUBLISHABLE_KEY:-}|" \ + -e "s|\${SUPABASE_SECRET_KEY}|${SUPABASE_SECRET_KEY:-}|" \ + -e "s|\${ANON_KEY_ASYMMETRIC}|${ANON_KEY_ASYMMETRIC:-}|" \ + -e "s|\${SERVICE_ROLE_KEY_ASYMMETRIC}|${SERVICE_ROLE_KEY_ASYMMETRIC:-}|" \ + -e "s|\${LUA_AUTH_EXPR}|${LUA_AUTH_EXPR}|" \ + -e "s|\${LUA_RT_WS_EXPR}|${LUA_RT_WS_EXPR}|" \ + -e "s|\${DASHBOARD_USERNAME}|${DASHBOARD_USERNAME}|" \ + -e "s|\${DASHBOARD_PASSWORD}|${DASHBOARD_PASSWORD}|" \ + /usr/local/kong/template.yml \ + > /usr/local/kong/kong.yml + + sed -i '/^[[:space:]]*- key:[[:space:]]*$/d' /usr/local/kong/kong.yml + + exec /entrypoint.sh kong docker-start + + kong.yml: | + _format_version: '2.1' + _transform: true + + consumers: + - username: DASHBOARD + - username: anon + keyauth_credentials: + - key: ${SUPABASE_ANON_KEY} + - key: ${SUPABASE_PUBLISHABLE_KEY} + - username: service_role + keyauth_credentials: + - key: ${SUPABASE_SERVICE_KEY} + - key: ${SUPABASE_SECRET_KEY} + acls: + - consumer: anon + group: anon + - consumer: service_role + group: admin + basicauth_credentials: + - consumer: DASHBOARD + username: '${DASHBOARD_USERNAME}' + password: '${DASHBOARD_PASSWORD}' + services: + - name: auth-v1-open + url: http://supabase-basket-supabase-auth:9999/verify + routes: + - name: auth-v1-open + strip_path: true + paths: + - /auth/v1/verify + plugins: + - name: cors + - name: auth-v1-open-callback + url: http://supabase-basket-supabase-auth:9999/callback + routes: + - name: auth-v1-open-callback + strip_path: true + paths: + - /auth/v1/callback + plugins: + - name: cors + - name: auth-v1-open-authorize + url: http://supabase-basket-supabase-auth:9999/authorize + routes: + - name: auth-v1-open-authorize + strip_path: true + paths: + - /auth/v1/authorize + plugins: + - name: cors + - name: auth-v1-open-jwks + url: http://supabase-basket-supabase-auth:9999/.well-known/jwks.json + routes: + - name: auth-v1-open-jwks + strip_path: true + paths: + - /auth/v1/.well-known/jwks.json + plugins: + - name: cors + - name: well-known-oauth + _comment: 'Auth: /.well-known/oauth-authorization-server -> http://supabase-basket-supabase-auth:9999/.well-known/oauth-authorization-server' + url: http://supabase-basket-supabase-auth:9999/.well-known/oauth-authorization-server + routes: + - name: well-known-oauth + strip_path: true + paths: + - /.well-known/oauth-authorization-server + plugins: + - name: cors + + - name: auth-v1-open-sso-acs + url: "http://auth:9999/sso/saml/acs" + routes: + - name: auth-v1-open-sso-acs + strip_path: true + paths: + - /sso/saml/acs + plugins: + - name: cors + - name: auth-v1-open-sso-metadata + url: "http://auth:9999/sso/saml/metadata" + routes: + - name: auth-v1-open-sso-metadata + strip_path: true + paths: + - /sso/saml/metadata + plugins: + - name: cors + - name: auth-v1 + _comment: "GoTrue: /auth/v1/* -> http://supabase-basket-supabase-auth:9999/*" + url: http://supabase-basket-supabase-auth:9999 + routes: + - name: auth-v1-all + strip_path: true + paths: + - /auth/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: request-transformer + config: + add: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + replace: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: rest-v1 + _comment: "PostgREST: /rest/v1/* -> http://supabase-basket-supabase-rest:3000/*" + url: http://supabase-basket-supabase-rest:3000/ + routes: + - name: rest-v1-all + strip_path: true + paths: + - /rest/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: request-transformer + config: + add: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + replace: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: graphql-v1 + _comment: 'PostgREST: /graphql/v1/* -> http://supabase-basket-supabase-rest:3000/rpc/graphql' + url: http://supabase-basket-supabase-rest:3000/rpc/graphql + routes: + - name: graphql-v1-all + strip_path: true + paths: + - /graphql/v1 + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: request-transformer + config: + add: + headers: + - "Content-Profile: graphql_public" + - "Authorization: ${LUA_AUTH_EXPR}" + replace: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: realtime-v1-ws + _comment: "Realtime: /realtime/v1/* -> ws://supabase-basket-supabase-realtime:4000/socket/*" + url: http://supabase-basket-supabase-realtime:4000/socket + protocol: ws + routes: + - name: realtime-v1-ws + strip_path: true + paths: + - /realtime/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: request-transformer + config: + add: + headers: + - "x-api-key:${LUA_RT_WS_EXPR}" + replace: + querystring: + - "apikey:${LUA_RT_WS_EXPR}" + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: realtime-v1-rest + _comment: 'Realtime: /realtime/v1/* -> http://supabase-basket-supabase-realtime:4000/api/*' + url: http://supabase-basket-supabase-realtime:4000/api + protocol: http + routes: + - name: realtime-v1-rest + strip_path: true + paths: + - /realtime/v1/api + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: request-transformer + config: + add: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + replace: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: storage-v1 + _comment: "Storage: /storage/v1/* -> http://supabase-basket-supabase-storage:5000/*" + url: http://supabase-basket-supabase-storage:5000/ + routes: + - name: storage-v1-all + strip_path: true + paths: + - /storage/v1/ + plugins: + - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + replace: + headers: + - "Authorization: ${LUA_AUTH_EXPR}" + - name: post-function + config: + access: + - | + local auth = kong.request.get_header("authorization") + if auth == nil or auth == "" or auth:find("^%s*$") then + kong.service.request.clear_header("authorization") + end + - name: functions-v1 + _comment: 'Edge Functions: /functions/v1/* -> http://supabase-basket-supabase-functions:9000/*' + url: http://supabase-basket-supabase-functions:9000/ + read_timeout: 150000 + routes: + - name: functions-v1-all + strip_path: true + paths: + - /functions/v1/ + plugins: + - name: cors + + + + + - name: meta + _comment: "pg-meta: /pg/* -> http://supabase-basket-supabase-meta:8080/*" + url: http://supabase-basket-supabase-meta:8080/ + routes: + - name: meta-all + strip_path: true + paths: + - /pg/ + plugins: + - name: key-auth + config: + hide_credentials: false + - name: acl + config: + hide_groups_header: true + allow: + - admin + - name: dashboard + _comment: 'Studio: /* -> http://supabase-basket-supabase-studio:3000/*' + url: http://supabase-basket-supabase-studio:3000/ + routes: + - name: dashboard-all + strip_path: true + paths: + - / + plugins: + - name: cors + - name: basic-auth + config: + hide_credentials: true +--- +# Source: supabase/templates/vector/config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: supabase-basket-supabase-vector-config + labels: + helm.sh/chart: supabase-0.5.6 + app.kubernetes.io/name: supabase + app.kubernetes.io/instance: supabase-basket + app.kubernetes.io/managed-by: Helm +data: + vector.yml: | + api: + enabled: true + address: 0.0.0.0:9001 + + sources: + kubernetes_host: + type: kubernetes_logs + extra_label_selector: app.kubernetes.io/instance=supabase-basket,app.kubernetes.io/name!=supabase-vector + + transforms: + project_logs: + type: remap + inputs: + - kubernetes_host + source: |- + .project = "default" + .event_message = del(.message) + .appname = del(.kubernetes.container_name) + del(.file) + del(.kubernetes) + del(.source_type) + del(.stream) + router: + type: route + inputs: + - project_logs + route: + kong: '.appname == "supabase-kong"' + auth: '.appname == "supabase-auth"' + rest: '.appname == "supabase-rest"' + realtime: '.appname == "supabase-realtime"' + storage: '.appname == "supabase-storage"' + functions: '.appname == "supabase-functions"' + db: '.appname == "supabase-db"' + # Ignores non nginx errors since they are related with kong booting up + kong_logs: + type: remap + inputs: + - router.kong + source: |- + req, err = parse_nginx_log(.event_message, "combined") + if err == null { + .timestamp = req.timestamp + .metadata.request.headers.referer = req.referer + .metadata.request.headers.user_agent = req.agent + .metadata.request.headers.cf_connecting_ip = req.client + .metadata.response.status_code = req.status + url, split_err = split(req.request, " ") + if split_err == null { + .metadata.request.method = url[0] + .metadata.request.path = url[1] + .metadata.request.protocol = url[2] + } + } + if err != null { + abort + } + # Ignores non nginx errors since they are related with kong booting up + kong_err: + type: remap + inputs: + - router.kong + source: |- + .metadata.request.method = "GET" + .metadata.response.status_code = 200 + parsed, err = parse_nginx_log(.event_message, "error") + if err == null { + .timestamp = parsed.timestamp + .severity = parsed.severity + .metadata.request.host = parsed.host + .metadata.request.headers.cf_connecting_ip = parsed.client + url, err = split(parsed.request, " ") + if err == null { + .metadata.request.method = url[0] + .metadata.request.path = url[1] + .metadata.request.protocol = url[2] + } + } + if err != null { + abort + } + # Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency. + auth_logs: + type: remap + inputs: + - router.auth + source: |- + parsed, err = parse_json(.event_message) + if err == null { + .metadata.timestamp = parsed.time + .metadata = merge!(.metadata, parsed) + } + # PostgREST logs are structured so we separate timestamp from message using regex + rest_logs: + type: remap + inputs: + - router.rest + source: |- + parsed, err = parse_regex(.event_message, r'^(?P