diff --git a/internal/db/reset/reset.go b/internal/db/reset/reset.go index fd5b7e711..40aee23f5 100644 --- a/internal/db/reset/reset.go +++ b/internal/db/reset/reset.go @@ -142,7 +142,7 @@ func resetDatabase15(ctx context.Context, version string, fsys afero.Fs, options } func initDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) error { - conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{User: "supabase_admin"}, options...) + conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{User: utils.SUPERUSER_ROLE}, options...) if err != nil { return err } @@ -152,7 +152,7 @@ func initDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) error { // Recreate postgres database by connecting to template1 func recreateDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) error { - conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{User: "supabase_admin", Database: "template1"}, options...) + conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{User: utils.SUPERUSER_ROLE, Database: "template1"}, options...) if err != nil { return err } diff --git a/internal/db/start/start.go b/internal/db/start/start.go index 7febc9294..6e28d917a 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -279,7 +279,7 @@ func initRealtimeJob(host string) utils.DockerJob { "PORT=4000", "DB_HOST=" + host, "DB_PORT=5432", - "DB_USER=supabase_admin", + "DB_USER=" + utils.SUPERUSER_ROLE, "DB_PASSWORD=" + utils.Config.Db.Password, "DB_NAME=postgres", "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime", diff --git a/internal/link/link_test.go b/internal/link/link_test.go index 21de43389..686f12d46 100644 --- a/internal/link/link_test.go +++ b/internal/link/link_test.go @@ -21,7 +21,6 @@ import ( "github.com/supabase/cli/pkg/api" "github.com/supabase/cli/pkg/migration" "github.com/supabase/cli/pkg/pgtest" - "github.com/supabase/cli/pkg/pgxv5" "github.com/zalando/go-keyring" ) @@ -48,7 +47,7 @@ func TestLinkCommand(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query(pgxv5.SET_SESSION_ROLE). + conn.Query(utils.SET_SESSION_ROLE). Reply("SET ROLE"). Query(GET_LATEST_STORAGE_MIGRATION). Reply("SELECT 1", []interface{}{"custom-metadata"}) @@ -198,7 +197,7 @@ func TestLinkCommand(t *testing.T) { // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) - conn.Query(pgxv5.SET_SESSION_ROLE). + conn.Query(utils.SET_SESSION_ROLE). Reply("SET ROLE"). Query(GET_LATEST_STORAGE_MIGRATION). Reply("SELECT 1", []interface{}{"custom-metadata"}) diff --git a/internal/start/start.go b/internal/start/start.go index 5e07d9dd1..3755f0daf 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -170,7 +170,7 @@ func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConf "DB_HOSTNAME=" + dbConfig.Host, fmt.Sprintf("DB_PORT=%d", dbConfig.Port), "DB_SCHEMA=_analytics", - "DB_USERNAME=supabase_admin", + "DB_USERNAME=" + utils.SUPERUSER_ROLE, "DB_PASSWORD=" + dbConfig.Password, "LOGFLARE_MIN_CLUSTER_SIZE=1", "LOGFLARE_SINGLE_TENANT=true", @@ -791,7 +791,7 @@ EOF "PORT=4000", "DB_HOST=" + dbConfig.Host, fmt.Sprintf("DB_PORT=%d", dbConfig.Port), - "DB_USER=supabase_admin", + "DB_USER=" + utils.SUPERUSER_ROLE, "DB_PASSWORD=" + dbConfig.Password, "DB_NAME=" + dbConfig.Database, "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime", diff --git a/internal/utils/connect.go b/internal/utils/connect.go index da58581fe..44aa0c03a 100644 --- a/internal/utils/connect.go +++ b/internal/utils/connect.go @@ -150,6 +150,12 @@ func ConnectByUrl(ctx context.Context, url string, options ...func(*pgx.ConnConf return pgxv5.Connect(ctx, url, options...) } +const ( + SUPERUSER_ROLE = "supabase_admin" + CLI_LOGIN_PREFIX = "cli_login_" + SET_SESSION_ROLE = "SET SESSION ROLE postgres" +) + func ConnectByConfigStream(ctx context.Context, config pgconn.Config, w io.Writer, options ...func(*pgx.ConnConfig)) (*pgx.Conn, error) { if IsLocalDatabase(config) { fmt.Fprintln(w, "Connecting to local database...") @@ -160,6 +166,13 @@ func ConnectByConfigStream(ctx context.Context, config pgconn.Config, w io.Write if DNSResolver.Value == DNS_OVER_HTTPS { cc.LookupFunc = FallbackLookupIP } + // Step down from platform provisioned login roles or privileged roles + if user := strings.Split(cc.User, ".")[0]; strings.EqualFold(user, SUPERUSER_ROLE) || + strings.HasPrefix(user, CLI_LOGIN_PREFIX) { + cc.AfterConnect = func(ctx context.Context, pgconn *pgconn.PgConn) error { + return pgconn.Exec(ctx, SET_SESSION_ROLE).Close() + } + } }) return ConnectByUrl(ctx, ToPostgresURL(config), opts...) } diff --git a/pkg/migration/queries/drop.sql b/pkg/migration/queries/drop.sql index 9ce1c8fbb..bbcf56edc 100644 --- a/pkg/migration/queries/drop.sql +++ b/pkg/migration/queries/drop.sql @@ -13,6 +13,7 @@ begin -- If an extension uses a schema it doesn't create, dropping the schema will cascade to also -- drop the extension. But if an extension creates its own schema, dropping the schema will -- throw an error. Hence, we drop schemas first while excluding those created by extensions. + raise notice 'dropping schema: %', rec.nspname; execute format('drop schema if exists %I cascade', rec.nspname); end loop; @@ -22,6 +23,7 @@ begin from pg_extension p where p.extname not in ('pg_graphql', 'pg_net', 'pg_stat_statements', 'pgcrypto', 'pgjwt', 'pgsodium', 'plpgsql', 'supabase_vault', 'uuid-ossp') loop + raise notice 'dropping extension: %', rec.extname; execute format('drop extension if exists %I cascade', rec.extname); end loop; @@ -32,6 +34,7 @@ begin where p.pronamespace::regnamespace::name = 'public' loop -- supports aggregate, function, and procedure + raise notice 'dropping function: %.%', rec.pronamespace::regnamespace::name, rec.proname; execute format('drop routine if exists %I.%I(%s) cascade', rec.pronamespace::regnamespace::name, rec.proname, pg_catalog.pg_get_function_identity_arguments(rec.oid)); end loop; @@ -43,6 +46,7 @@ begin c.relnamespace::regnamespace::name = 'public' and c.relkind = 'v' loop + raise notice 'dropping view: %.%', rec.relnamespace::regnamespace::name, rec.relname; execute format('drop view if exists %I.%I cascade', rec.relnamespace::regnamespace::name, rec.relname); end loop; @@ -54,6 +58,7 @@ begin c.relnamespace::regnamespace::name = 'public' and c.relkind = 'm' loop + raise notice 'dropping materialized view: %.%', rec.relnamespace::regnamespace::name, rec.relname; execute format('drop materialized view if exists %I.%I cascade', rec.relnamespace::regnamespace::name, rec.relname); end loop; @@ -67,6 +72,7 @@ begin order by c.relkind desc loop -- supports all table like relations, except views, complex types, and sequences + raise notice 'dropping table: %.%', rec.relnamespace::regnamespace::name, rec.relname; execute format('drop table if exists %I.%I cascade', rec.relnamespace::regnamespace::name, rec.relname); end loop; @@ -80,6 +86,7 @@ begin or c.relnamespace::regnamespace::name = 'supabase_migrations') and c.relkind = 'r' loop + raise notice 'truncating table: %.%', rec.relnamespace::regnamespace::name, rec.relname; execute format('truncate %I.%I cascade', rec.relnamespace::regnamespace::name, rec.relname); end loop; @@ -91,6 +98,7 @@ begin c.relnamespace::regnamespace::name = 'public' and c.relkind = 's' loop + raise notice 'dropping sequence: %.%', rec.relnamespace::regnamespace::name, rec.relname; execute format('drop sequence if exists %I.%I cascade', rec.relnamespace::regnamespace::name, rec.relname); end loop; @@ -102,6 +110,7 @@ begin t.typnamespace::regnamespace::name = 'public' and typtype != 'b' loop + raise notice 'dropping type: %.%', rec.typnamespace::regnamespace::name, rec.typname; execute format('drop type if exists %I.%I cascade', rec.typnamespace::regnamespace::name, rec.typname); end loop; @@ -110,6 +119,7 @@ begin select * from pg_policies p loop + raise notice 'dropping policy: %', rec.policyname; execute format('drop policy if exists %I on %I.%I cascade', rec.policyname, rec.schemaname, rec.tablename); end loop; @@ -120,6 +130,7 @@ begin where not p.pubname like any(array['supabase\_realtime%', 'realtime\_messages%']) loop + raise notice 'dropping publication: %', rec.pubname; execute format('drop publication if exists %I', rec.pubname); end loop; end $$; diff --git a/pkg/pgxv5/connect.go b/pkg/pgxv5/connect.go index 528742921..4ae754f24 100644 --- a/pkg/pgxv5/connect.go +++ b/pkg/pgxv5/connect.go @@ -11,11 +11,6 @@ import ( "github.com/jackc/pgx/v4" ) -const ( - CLI_LOGIN_PREFIX = "cli_login_" - SET_SESSION_ROLE = "SET SESSION ROLE postgres" -) - // Extends pgx.Connect with support for programmatically overriding parsed config func Connect(ctx context.Context, connString string, options ...func(*pgx.ConnConfig)) (*pgx.Conn, error) { // Parse connection url @@ -28,11 +23,6 @@ func Connect(ctx context.Context, connString string, options ...func(*pgx.ConnCo fmt.Fprintf(os.Stderr, "%s (%s): %s\n", n.Severity, n.Code, n.Message) } } - if strings.HasPrefix(config.User, CLI_LOGIN_PREFIX) { - config.AfterConnect = func(ctx context.Context, pgconn *pgconn.PgConn) error { - return pgconn.Exec(ctx, SET_SESSION_ROLE).Close() - } - } // Apply config overrides for _, op := range options { op(config)