providers/proxy: move search path to query instead of runtime parameter (#20662)

Co-authored-by: Dominic R <dominic@sdko.org>
This commit is contained in:
Jens L.
2026-03-03 17:49:28 +00:00
committed by GitHub
parent 8fccf27b38
commit ec7efa53cb
2 changed files with 108 additions and 7 deletions
@@ -187,10 +187,7 @@ func BuildConnConfig(cfg config.PostgreSQLConfig) (*pgx.ConnConfig, error) {
if connConfig.RuntimeParams == nil {
connConfig.RuntimeParams = make(map[string]string)
}
if cfg.DefaultSchema != "" {
connConfig.RuntimeParams["search_path"] = cfg.DefaultSchema
}
effectiveSearchPath := cfg.DefaultSchema
// Parse and apply connection options if specified
if cfg.ConnOptions != "" {
@@ -198,12 +195,39 @@ func BuildConnConfig(cfg config.PostgreSQLConfig) (*pgx.ConnConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse connection options: %w", err)
}
// search_path from ConnOptions is not supported here; Django controls schema selection.
// Always remove it so it cannot end up in startup RuntimeParams via applyConnOptions.
delete(connOpts, "search_path")
if err := applyConnOptions(connConfig, connOpts); err != nil {
return nil, fmt.Errorf("failed to apply connection options: %w", err)
}
}
// search_path may already be present via pgx/libpq inherited defaults (e.g. service files).
// Always remove it from startup RuntimeParams; apply it via AfterConnect instead.
if inheritedSearchPath, hasInheritedSearchPath := connConfig.RuntimeParams["search_path"]; hasInheritedSearchPath {
if effectiveSearchPath == "" {
effectiveSearchPath = inheritedSearchPath
}
delete(connConfig.RuntimeParams, "search_path")
}
// Set search_path after connection startup to avoid startup-parameter issues with PgBouncer.
if effectiveSearchPath != "" {
connConfig.AfterConnect = func(ctx context.Context, pgConn *pgconn.PgConn) error {
result := pgConn.ExecParams(
ctx,
"select pg_catalog.set_config('search_path', $1, false)",
[][]byte{[]byte(effectiveSearchPath)},
nil,
nil,
nil,
).Read()
return result.Err
}
}
return connConfig, nil
}
@@ -700,7 +700,7 @@ func TestBuildConnConfig(t *testing.T) {
DefaultSchema: "custom_schema",
},
validate: func(t *testing.T, cc *pgx.ConnConfig) {
assert.Equal(t, "custom_schema", cc.RuntimeParams["search_path"])
assert.NotNil(t, cc.AfterConnect)
},
},
{
@@ -756,7 +756,7 @@ func TestBuildConnConfig(t *testing.T) {
assert.Equal(t, "admin", cc.User)
assert.Equal(t, "my super secret password!@#", cc.Password)
assert.Equal(t, "production", cc.Database)
assert.Equal(t, "app_schema", cc.RuntimeParams["search_path"])
assert.NotNil(t, cc.AfterConnect)
assert.Equal(t, "authentik", cc.RuntimeParams["application_name"])
},
},
@@ -863,7 +863,7 @@ func TestBuildConnConfig_WithSSLCertificates(t *testing.T) {
assert.Equal(t, "db.example.com", cc.TLSConfig.ServerName)
assert.NotNil(t, cc.TLSConfig.RootCAs)
assert.Len(t, cc.TLSConfig.Certificates, 1)
assert.Equal(t, "app_schema", cc.RuntimeParams["search_path"])
assert.NotNil(t, cc.AfterConnect)
assert.Equal(t, "authentik", cc.RuntimeParams["application_name"])
},
},
@@ -1357,6 +1357,83 @@ func TestBuildConnConfig_WithBase64EncodedConnOptions(t *testing.T) {
}
}
// Verifies DefaultSchema is applied via AfterConnect and never via startup RuntimeParams.
func TestBuildConnConfig_SearchPath_DefaultSchema(t *testing.T) {
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
DefaultSchema: "default_schema",
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
require.NotNil(t, connConfig.AfterConnect)
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// Verifies ConnOptions search_path is ignored and excluded from startup RuntimeParams.
func TestBuildConnConfig_SearchPath_ConnOptions(t *testing.T) {
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
ConnOptions: base64.StdEncoding.EncodeToString([]byte(`{"search_path":"connopt_schema"}`)),
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
assert.Nil(t, connConfig.AfterConnect)
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// Verifies ConnOptions search_path does not override DefaultSchema and other conn options still apply.
func TestBuildConnConfig_SearchPath_ConnOptionsIgnoredWhenDefaultSchemaSet(t *testing.T) {
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
DefaultSchema: "default_schema",
ConnOptions: base64.StdEncoding.EncodeToString([]byte(`{"search_path":"override_schema","application_name":"authentik-proxy"}`)),
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
require.NotNil(t, connConfig.AfterConnect)
assert.Equal(t, "authentik-proxy", connConfig.RuntimeParams["application_name"])
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// Verifies inherited search_path from pgx/libpq defaults is removed from startup RuntimeParams.
func TestBuildConnConfig_SearchPath_InheritedServiceSetting(t *testing.T) {
serviceFile := filepath.Join(t.TempDir(), "pg_service.conf")
err := os.WriteFile(serviceFile, []byte("[authentik-test]\nsearch_path=service_schema\n"), 0o600)
require.NoError(t, err)
t.Setenv("PGSERVICE", "authentik-test")
t.Setenv("PGSERVICEFILE", serviceFile)
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
require.NotNil(t, connConfig.AfterConnect)
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// TestBuildConnConfig_TargetSessionAttrs demonstrates how target_session_attrs
// should be properly handled using pgx's ValidateConnect callback
func TestBuildConnConfig_TargetSessionAttrs(t *testing.T) {