Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions e2e/postgrest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ import (
)

// postgrestSpec returns a ServiceSpec for a PostgREST service on the given host.
// PostgREST connects as the "admin" database user (connect_as).
// PostgREST connects as the "admin" database user (connect_as) and defaults to
// "anon" as the db_anon_role when not explicitly provided.
func postgrestSpec(hostID string, port int, config map[string]any) *controlplane.ServiceSpec {
if config == nil {
config = map[string]any{}
}
if _, ok := config["db_anon_role"]; !ok {
config["db_anon_role"] = "anon"
}
return &controlplane.ServiceSpec{
ServiceID: "postgrest-api",
ServiceType: "postgrest",
Expand Down Expand Up @@ -55,6 +59,10 @@ func postgrestBaseSpec(dbName string, nodeHosts []string, services []*controlpla
DbOwner: pointerTo(true),
Attributes: []string{"LOGIN", "SUPERUSER"},
},
{
Username: "anon",
Attributes: []string{"NOLOGIN"},
},
},
Port: pointerTo(0),
Nodes: nodes,
Expand Down Expand Up @@ -307,7 +315,7 @@ func TestPostgRESTAuthenticatorRole(t *testing.T) {
require.NoError(t, err, "connect_as user 'admin' must exist in pg_roles")
assert.False(t, rolinherit, "connect_as user must have NOINHERIT (rolinherit = false)")

// The db_anon_role (pgedge_application_read_only by default) must be granted to the connect_as user.
// The db_anon_role ("anon") must be granted to the connect_as user.
var anonGranted bool
err = conn.QueryRow(ctx, `
SELECT EXISTS (
Expand All @@ -316,7 +324,7 @@ func TestPostgRESTAuthenticatorRole(t *testing.T) {
JOIN pg_roles r ON m.member = r.oid
JOIN pg_roles g ON m.roleid = g.oid
WHERE r.rolname = 'admin'
AND g.rolname = 'pgedge_application_read_only'
AND g.rolname = 'anon'
)
`).Scan(&anonGranted)
require.NoError(t, err)
Expand Down Expand Up @@ -492,6 +500,10 @@ func TestPostgRESTMultiHostDBURI(t *testing.T) {
DbOwner: pointerTo(true),
Attributes: []string{"LOGIN", "SUPERUSER"},
},
{
Username: "anon",
Attributes: []string{"NOLOGIN"},
},
},
Port: pointerTo(0),
Nodes: []*controlplane.DatabaseNodeSpec{
Expand Down Expand Up @@ -551,6 +563,10 @@ func TestPostgRESTFailover(t *testing.T) {
DbOwner: pointerTo(true),
Attributes: []string{"LOGIN", "SUPERUSER"},
},
{
Username: "anon",
Attributes: []string{"NOLOGIN"},
},
},
Port: pointerTo(0),
Nodes: []*controlplane.DatabaseNodeSpec{
Expand Down
26 changes: 15 additions & 11 deletions server/internal/api/apiv1/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ func TestValidateDatabaseSpec(t *testing.T) {
HostIds: []api.Identifier{"host-1"},
ConnectAs: "app",
Port: utils.PointerTo(8080),
Config: map[string]any{},
Config: map[string]any{"db_anon_role": "web_anon"},
},
},
},
Expand Down Expand Up @@ -841,7 +841,7 @@ func TestValidateDatabaseSpec(t *testing.T) {
HostIds: []api.Identifier{"host-2"},
ConnectAs: "app",
Port: utils.PointerTo(8080),
Config: map[string]any{},
Config: map[string]any{"db_anon_role": "web_anon"},
},
},
},
Expand Down Expand Up @@ -912,7 +912,7 @@ func TestValidateDatabaseSpec(t *testing.T) {
Version: "1.0.0",
HostIds: []api.Identifier{"host-1"},
ConnectAs: "app",
Config: map[string]any{},
Config: map[string]any{"db_anon_role": "web_anon"},
},
},
},
Expand Down Expand Up @@ -953,7 +953,7 @@ func TestValidateDatabaseSpec(t *testing.T) {
HostIds: []api.Identifier{"host-1"},
ConnectAs: "app",
Port: utils.PointerTo(0),
Config: map[string]any{},
Config: map[string]any{"db_anon_role": "web_anon"},
},
},
},
Expand Down Expand Up @@ -1341,7 +1341,7 @@ func TestValidateServiceSpec(t *testing.T) {
ServiceType: "postgrest",
Version: "14.5",
HostIds: []api.Identifier{"host-1"},
Config: map[string]any{},
Config: map[string]any{"db_anon_role": "web_anon"},
},
},
{
Expand Down Expand Up @@ -1417,13 +1417,14 @@ func TestValidateServiceSpec(t *testing.T) {
},
},
{
name: "valid postgrest with nil config",
name: "postgrest with nil config fails — db_anon_role required",
svc: &api.ServiceSpec{
ServiceID: "my-postgrest",
ServiceType: "postgrest",
Version: "latest",
HostIds: []api.Identifier{"host-1"},
},
expected: []string{"db_anon_role is required"},
},
{
name: "RAG service with nil config requires pipelines",
Expand All @@ -1438,15 +1439,15 @@ func TestValidateServiceSpec(t *testing.T) {
},
},
{
name: "valid postgrest with defaults",
name: "postgrest with empty config fails — db_anon_role required",
svc: &api.ServiceSpec{
ServiceID: "my-postgrest",
ServiceType: "postgrest",
Version: "latest",
HostIds: []api.Identifier{"host-1"},
Config: map[string]any{},
},
expected: []string{},
expected: []string{"db_anon_role is required"},
},
{
name: "valid postgrest with all config fields",
Expand All @@ -1472,7 +1473,8 @@ func TestValidateServiceSpec(t *testing.T) {
Version: "latest",
HostIds: []api.Identifier{"host-1"},
Config: map[string]any{
"db_pool": float64(99),
"db_anon_role": "anon",
"db_pool": float64(99),
},
},
expected: []string{
Expand All @@ -1487,7 +1489,8 @@ func TestValidateServiceSpec(t *testing.T) {
Version: "latest",
HostIds: []api.Identifier{"host-1"},
Config: map[string]any{
"invalid_key": "value",
"db_anon_role": "anon",
"invalid_key": "value",
},
},
expected: []string{
Expand All @@ -1502,7 +1505,8 @@ func TestValidateServiceSpec(t *testing.T) {
Version: "latest",
HostIds: []api.Identifier{"host-1"},
Config: map[string]any{
"jwt_secret": "tooshort",
"db_anon_role": "anon",
"jwt_secret": "tooshort",
},
},
expected: []string{
Expand Down
14 changes: 8 additions & 6 deletions server/internal/database/postgrest_service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
)

// PostgRESTServiceConfig is the typed internal representation of PostgREST service
// configuration. Parsed from ServiceSpec.Config map[string]any. All fields are
// optional; defaults are applied when absent.
// configuration. Parsed from ServiceSpec.Config map[string]any. DBAnonRole is
// required; all other fields are optional with defaults applied when absent.
type PostgRESTServiceConfig struct {
DBSchemas string `json:"db_schemas"` // default: "public"
DBAnonRole string `json:"db_anon_role"` // default: "pgedge_application_read_only"
DBAnonRole string `json:"db_anon_role"` // required: no default
DBPool int `json:"db_pool"` // default: 10, range: 1-30
MaxRows int `json:"max_rows"` // default: 1000, range: 1-10000
JWTSecret *string `json:"jwt_secret,omitempty"`
Expand Down Expand Up @@ -53,9 +53,11 @@ func ParsePostgRESTServiceConfig(config map[string]any) (*PostgRESTServiceConfig
}
}

// db_anon_role — optional string, default "pgedge_application_read_only"
dbAnonRole := "pgedge_application_read_only"
if v, ok := config["db_anon_role"]; ok {
// db_anon_role — required string
var dbAnonRole string
if v, ok := config["db_anon_role"]; !ok {
errs = append(errs, fmt.Errorf("db_anon_role is required"))
} else {
s, sOk := v.(string)
if !sOk {
errs = append(errs, fmt.Errorf("db_anon_role must be a string"))
Expand Down
Loading