diff --git a/api/config.go b/api/config.go index b17b9fde..5e541a3d 100644 --- a/api/config.go +++ b/api/config.go @@ -10,11 +10,12 @@ import ( var DefaultConfig = Config{ Postgrest: PostgrestConfig{ - Version: "v10.0.0", - DBRole: "postgrest_api", - Port: 3000, - AdminPort: 3001, - MaxRows: 2000, + Version: "v10.0.0", + DBRole: "postgrest_api", + AnonDBRole: "", + Port: 3000, + AdminPort: 3001, + MaxRows: 2000, }, } @@ -110,14 +111,15 @@ func (c Config) GetUsername() string { } type PostgrestConfig struct { - Port int - Disable bool - LogLevel string - URL string - Version string - JWTSecret string - DBRole string - AdminPort int + Port int + Disable bool + LogLevel string + URL string + Version string + JWTSecret string + DBRole string + AnonDBRole string + AdminPort int // A hard limit to the number of rows PostgREST will fetch from a view, table, or stored procedure. // Limits payload size for accidental or malicious requests. diff --git a/db.go b/db.go index e3b20fd7..fd22cd61 100644 --- a/db.go +++ b/db.go @@ -237,6 +237,12 @@ func setStatementTimeouts(ctx dutyContext.Context, config api.Config) { logger.Errorf(err.Error()) } + if config.Postgrest.AnonDBRole != "" { + if err := ctx.DB().Raw(fmt.Sprintf(`ALTER ROLE %s SET statement_timeout = '%0fs'`, config.Postgrest.AnonDBRole, postgrestTimeout.Seconds())).Error; err != nil { + logger.Errorf(err.Error()) + } + } + statementTimeout := ctx.Properties().Duration("db.connection.timeout", 1*time.Hour) if username := config.GetUsername(); username != "" { if err := ctx.DB().Raw(fmt.Sprintf(`ALTER ROLE %s SET statement_timeout = '%0fs'`, username, statementTimeout.Seconds())).Error; err != nil { diff --git a/functions/postgrest.sql b/functions/postgrest.sql index ab946f6b..2fa25dda 100644 --- a/functions/postgrest.sql +++ b/functions/postgrest.sql @@ -9,5 +9,7 @@ BEGIN IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'postgrest_anon') THEN CREATE ROLE postgrest_anon; + GRANT SELECT ON ALL TABLES IN SCHEMA public TO postgrest_anon; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO postgrest_anon; END IF; END $$; diff --git a/migrate/migrate.go b/migrate/migrate.go index 58eea102..c3835842 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -86,40 +86,63 @@ func RunMigrations(pool *sql.DB, config api.Config) error { return nil } -func grantPostgrestRolesToCurrentUser(pool *sql.DB, config api.Config) error { - l := logger.GetLogger("migrate") - - user := config.GetUsername() - if user == "" { - return fmt.Errorf("Cannot find username in connection string") +func createRole(db *sql.DB, roleName string, config api.Config, grants ...string) error { + if roleName == "" { + return nil } - role := config.Postgrest.DBRole - if isPostgrestAPIGranted, err := checkIfRoleIsGranted(pool, role, user); err != nil { + log := logger.GetLogger("migrate") + count := 0 + if err := db.QueryRow("SELECT count(*) FROM pg_catalog.pg_roles WHERE rolname = $1 LIMIT 1", roleName).Scan(&count); err != nil { return err - } else if !isPostgrestAPIGranted { - if _, err := pool.Exec(fmt.Sprintf(`GRANT %s TO "%s"`, role, user)); err != nil { + } else if count == 0 { + if _, err := db.Exec(fmt.Sprintf("CREATE ROLE %s", roleName)); err != nil { + return err + } else { + log.Infof("Created role %s", roleName) + } + } + user := config.GetUsername() + if user == "" { + log.Errorf("Unable to find current user, %s may not be setup correctly", roleName) + } else { + if granted, err := checkIfRoleIsGranted(db, roleName, user); err != nil { return err + } else if !granted { + if _, err := db.Exec(fmt.Sprintf(`GRANT %s TO "%s"`, roleName, user)); err != nil { + log.Errorf("Failed to grant role %s to %s", roleName, user) + } else { + log.Infof("Granted %s to %s", roleName, user) + } } - l.Debugf("Granted %s to %s", role, user) - - grantQuery := ` - ALTER ROLE %s SET statement_timeout = '120s'; - GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO %s; - GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO %s; - GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO %s; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO %s; - ` - grantQuery = fmt.Sprintf(grantQuery, role, role, role, role, role) - if _, err := pool.Exec(grantQuery); err != nil { - return oops.Hint(grantQuery).Wrap(err) + } + + for _, grant := range grants { + if _, err := db.Exec(fmt.Sprintf(grant, roleName)); err != nil { + log.Errorf("Failed to apply grants for %s: %+v", roleName, err) } - l.Debugf("Granted privileges to %s", user) } return nil } +func grantPostgrestRolesToCurrentUser(pool *sql.DB, config api.Config) error { + if err := createRole(pool, config.Postgrest.DBRole, config, + "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO %s", + "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO %s", + "GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO %s", + "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO %s"); err != nil { + return err + } + if err := createRole(pool, config.Postgrest.AnonDBRole, config, + "GRANT SELECT ON ALL TABLES IN SCHEMA public TO %s", + "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO %s"); err != nil { + return err + } + + return nil +} + func checkIfRoleIsGranted(pool *sql.DB, group, member string) (bool, error) { query := ` SELECT COUNT(*) diff --git a/postgrest/postgrest.go b/postgrest/postgrest.go index c93099ce..f3a846c4 100644 --- a/postgrest/postgrest.go +++ b/postgrest/postgrest.go @@ -18,7 +18,7 @@ func getBinary(config api.Config) deps.BinaryFunc { "PGRST_SERVER_PORT": strconv.Itoa(config.Postgrest.Port), "PGRST_DB_URI": config.ConnectionString, "PGRST_DB_SCHEMA": config.Schema, - "PGRST_DB_ANON_ROLE": "", + "PGRST_DB_ANON_ROLE": config.Postgrest.AnonDBRole, "PGRST_OPENAPI_SERVER_PROXY_URI": config.Postgrest.URL, "PGRST_LOG_LEVEL": config.Postgrest.LogLevel, "PGRST_DB_MAX_ROWS": strconv.Itoa(config.Postgrest.MaxRows), diff --git a/start.go b/start.go index c97df8f5..6c522e18 100644 --- a/start.go +++ b/start.go @@ -34,6 +34,8 @@ func BindPFlags(flags *pflag.FlagSet, opts ...StartOption) { flags.StringVar(&DefaultConfig.Postgrest.JWTSecret, "postgrest-jwt-secret", "PGRST_JWT_SECRET", "JWT Secret Token for PostgREST") flags.BoolVar(&DefaultConfig.Postgrest.Disable, "disable-postgrest", config.Postgrest.Disable, "Disable PostgREST. Deprecated (Use --postgrest-uri '' to disable PostgREST)") flags.StringVar(&DefaultConfig.Postgrest.DBRole, "postgrest-role", "postgrest_api", "PostgREST role for authentication connections") + flags.StringVar(&DefaultConfig.Postgrest.AnonDBRole, "postgrest-anon-role", "postgrest_anon", "PostgREST role for unauthenticated connections") + flags.IntVar(&DefaultConfig.Postgrest.MaxRows, "postgrest-max-rows", 2000, "A hard limit to the number of rows PostgREST will fetch") flags.StringVar(&DefaultConfig.LogLevel, "db-log-level", "error", "Set gorm logging level. trace, debug & info") flags.BoolVar(&DefaultConfig.DisableKubernetes, "disable-kubernetes", false, "Disable Kubernetes integration")