From 0a18aba91cd5a4039f2fcb098a6a3fe50a032972 Mon Sep 17 00:00:00 2001
From: Silvan <27845747+adlerhurst@users.noreply.github.com>
Date: Wed, 5 Aug 2026 09:28:16 +0200
Subject: [PATCH] fix(database): skip CREATE USER when role exists to avoid
password in Postgres logs (#12538)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## 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`
Co-authored-by: Cursor Agent
Co-authored-by: Silvan
---
cmd/initialise/verify_user.go | 34 ++++++++++++++++++---
cmd/initialise/verify_user_test.go | 48 ++++++++++++++++++++++++++++--
2 files changed, 75 insertions(+), 7 deletions(-)
diff --git a/cmd/initialise/verify_user.go b/cmd/initialise/verify_user.go
index d821216f4..a574ee9f4 100644
--- a/cmd/initialise/verify_user.go
+++ b/cmd/initialise/verify_user.go
@@ -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, "'", "''") + "'"
+}
diff --git a/cmd/initialise/verify_user_test.go b/cmd/initialise/verify_user_test.go
index 442543265..fdda6e189 100644
--- a/cmd/initialise/verify_user_test.go
+++ b/cmd/initialise/verify_user_test.go
@@ -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{