fix(database): skip CREATE USER when role exists to avoid password in Postgres logs (#12538)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

When `zitadel init` runs on every Helm upgrade/re-deploy, `VerifyUser`
always executed `CREATE USER ... WITH PASSWORD '...'`. If the role
already existed, PostgreSQL returned `42710` and logged the full
statement—including the plaintext password—even though ZITADEL swallowed
that error.

This mirrors the existing `VerifyDatabase` catalog check: query
`pg_roles` first and skip creation when the role exists. Also stops
mutating the package-level `createUserStmt` when appending `WITH
PASSWORD`.

Fixes #12178

## Changes

- `cmd/initialise/verify_user.go`: check `SELECT EXISTS(... FROM
pg_roles ...)` before `CREATE USER`; build password clause on a local
statement copy
- `cmd/initialise/verify_user_test.go`: cover catalog skip,
catalog-check failure, and create paths with the new query

## Test plan

- [x] `go test ./cmd/initialise/...` (passed)
- [ ] Re-run init against an existing role and confirm Postgres logs no
longer contain `CREATE USER ... WITH PASSWORD`
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-bc514793-d275-4d91-a71b-1b7566668b65?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-bc514793-d275-4d91-a71b-1b7566668b65&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Silvan <[email protected]>
This commit is contained in:
Silvan
2026-08-05 09:28:16 +02:00
committed by GitHub
co-authored by Cursor Agent Silvan
parent 464d1cb655
commit 0a18aba91c
2 changed files with 75 additions and 7 deletions
+30 -4
View File
@@ -6,6 +6,7 @@ import (
_ "embed"
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -58,11 +59,36 @@ func VerifyUser(username, password string) func(context.Context, *database.DB) e
logging.Info(ctx, "config.database.postgres.user.username is same as config.database.postgres.admin.username, skipping create user", "username", username)
return nil
}
logging.Info(ctx, "verify user", "username", username)
if password != "" {
createUserStmt += " WITH PASSWORD '" + password + "'"
// Check if the role already exists in the catalog before attempting CREATE USER.
// This avoids PostgreSQL logging the full CREATE USER statement (including the
// plaintext password) when the role already exists on re-deploy / re-init.
var exists bool
err = db.QueryRowContext(ctx, func(r *sql.Row) error {
return r.Scan(&exists)
}, "SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)", username)
if err != nil {
return fmt.Errorf("unable to check if user exists: %w", err)
}
if exists {
logging.Info(ctx, "user already exists, skipping creation", "username", username)
return nil
}
return exec(ctx, db, fmt.Sprintf(createUserStmt, username), []string{roleAlreadyExistsCode})
logging.Info(ctx, "verify user", "username", username)
// Format the username first so the password is never part of a fmt format string
// (passwords may contain '%' which would be interpreted as format verbs).
stmt := fmt.Sprintf(createUserStmt, username)
if password != "" {
stmt += " WITH PASSWORD " + quotePostgresLiteral(password)
}
return exec(ctx, db, stmt, []string{roleAlreadyExistsCode})
}
}
// quotePostgresLiteral returns s as a single-quoted PostgreSQL string literal,
// escaping embedded single quotes by doubling them.
func quotePostgresLiteral(s string) string {
return "'" + strings.ReplaceAll(s, "'", "''") + "'"
}
+45 -3
View File
@@ -32,6 +32,9 @@ func Test_verifyUser(t *testing.T) {
expectQuery("SELECT current_user", nil, []string{"current_user"}, [][]driver.Value{
{"postgres"},
}),
expectQuery("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)", nil, []string{"exists"}, [][]driver.Value{
{false},
}, "zitadel-user"),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER \"zitadel-user\"", sql.ErrTxDone),
),
username: "zitadel-user",
@@ -46,6 +49,9 @@ func Test_verifyUser(t *testing.T) {
expectQuery("SELECT current_user", nil, []string{"current_user"}, [][]driver.Value{
{"postgres"},
}),
expectQuery("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)", nil, []string{"exists"}, [][]driver.Value{
{false},
}, "zitadel-user"),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER \"zitadel-user\"", nil),
),
username: "zitadel-user",
@@ -60,6 +66,9 @@ func Test_verifyUser(t *testing.T) {
expectQuery("SELECT current_user", nil, []string{"current_user"}, [][]driver.Value{
{"postgres"},
}),
expectQuery("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)", nil, []string{"exists"}, [][]driver.Value{
{false},
}, "zitadel-user"),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER \"zitadel-user\" WITH PASSWORD 'password'", nil),
),
username: "zitadel-user",
@@ -68,19 +77,52 @@ func Test_verifyUser(t *testing.T) {
targetErr: nil,
},
{
name: "already exists",
name: "correct with password containing percent and quote",
args: args{
db: prepareDB(t,
expectQuery("SELECT current_user", nil, []string{"current_user"}, [][]driver.Value{
{"postgres"},
}),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER \"zitadel-user\" WITH PASSWORD 'password'", nil),
expectQuery("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)", nil, []string{"exists"}, [][]driver.Value{
{false},
}, "zitadel-user"),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER \"zitadel-user\" WITH PASSWORD 'p%''ass'", nil),
),
username: "zitadel-user",
password: "",
password: "p%'ass",
},
targetErr: nil,
},
{
name: "already exists in catalog, skip creation",
args: args{
db: prepareDB(t,
expectQuery("SELECT current_user", nil, []string{"current_user"}, [][]driver.Value{
{"postgres"},
}),
expectQuery("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)", nil, []string{"exists"}, [][]driver.Value{
{true},
}, "zitadel-user"),
),
username: "zitadel-user",
password: "password",
},
targetErr: nil,
},
{
name: "catalog check fails",
args: args{
db: prepareDB(t,
expectQuery("SELECT current_user", nil, []string{"current_user"}, [][]driver.Value{
{"postgres"},
}),
expectQuery("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)", sql.ErrConnDone, []string{"exists"}, [][]driver.Value{}, "zitadel-user"),
),
username: "zitadel-user",
password: "password",
},
targetErr: sql.ErrConnDone,
},
{
name: "same user, skip create",
args: args{