New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create user in envtool
for PostgreSQL
#4133
Changes from all commits
36c81c0
d3a2bef
167ccf6
2043e92
b2f6205
ff25f6f
d68a777
1f1c728
af0bdff
563e165
96d34ef
9db3718
34bd803
f41adb2
80bf3a0
aba7e4e
6192940
4c4add2
8d5ca45
5278da8
3134785
5801476
afae527
0b57bbe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,8 @@ import ( | |
"time" | ||
|
||
"github.com/alecthomas/kong" | ||
"github.com/jackc/pgerrcode" | ||
"github.com/jackc/pgx/v5/pgconn" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"go.uber.org/zap" | ||
"go.uber.org/zap/zapcore" | ||
|
@@ -52,6 +54,12 @@ var ( | |
//go:embed error.tmpl | ||
errorTemplateB []byte | ||
|
||
//go:embed test_user.json | ||
testUser string | ||
|
||
//go:embed test_system_users.json | ||
testSystemUsers string | ||
|
||
// Parsed error template. | ||
errorTemplate = template.Must(template.New("error").Option("missingkey=error").Parse(string(errorTemplateB))) | ||
) | ||
|
@@ -136,14 +144,46 @@ func setupAnyPostgres(ctx context.Context, logger *zap.SugaredLogger, uri string | |
} | ||
|
||
// setupPostgres configures `postgres` container. | ||
// | ||
// It also creates a user in ferretdb and template1 databases so all subsequent | ||
// databases created from template1 have the user. | ||
// See also https://www.postgresql.org/docs/current/manage-ag-templatedbs.html. | ||
func setupPostgres(ctx context.Context, logger *zap.SugaredLogger) error { | ||
l := logger.Named("postgres") | ||
|
||
// user `username` must exist, but password may be any, even empty | ||
return setupAnyPostgres(ctx, logger.Named("postgres"), "postgres://username@127.0.0.1:5432/ferretdb") | ||
err := setupAnyPostgres(ctx, l, "postgres://username@127.0.0.1:5432/ferretdb") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = setupUserInPostgres(ctx, logger, "postgres://username@127.0.0.1:5432/ferretdb") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return setupUserInPostgres(ctx, logger, "postgres://username@127.0.0.1:5432/template1") | ||
} | ||
|
||
// setupPostgresSecured configures `postgres_secured` container. | ||
// | ||
// It also creates a user in ferretdb and template1 databases so all subsequent | ||
// databases created from template1 have the user. | ||
// See also https://www.postgresql.org/docs/current/manage-ag-templatedbs.html. | ||
func setupPostgresSecured(ctx context.Context, logger *zap.SugaredLogger) error { | ||
return setupAnyPostgres(ctx, logger.Named("postgres_secured"), "postgres://username:password@127.0.0.1:5433/ferretdb") | ||
l := logger.Named("postgres_secured") | ||
|
||
err := setupAnyPostgres(ctx, l, "postgres://username:password@127.0.0.1:5433/ferretdb") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = setupUserInPostgres(ctx, logger, "postgres://username:password@127.0.0.1:5433/ferretdb") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return setupUserInPostgres(ctx, logger, "postgres://username:password@127.0.0.1:5433/template1") | ||
Comment on lines
+181
to
+186
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why those calls are not inside |
||
} | ||
|
||
// setupMySQL configures `mysql` container. | ||
|
@@ -219,6 +259,92 @@ func setupMongodb(ctx context.Context, logger *zap.SugaredLogger) error { | |
return ctx.Err() | ||
} | ||
|
||
// setupUserInPostgres creates a user with username/password credential in admin database | ||
// with supported mechanisms. | ||
// It creates admin database (PostgreSQL admin schema), if it does not exist. | ||
// It also creates system.users collection, if it does not exist. | ||
// | ||
// Without this, once the first user is created, the authentication fails | ||
// as username/password does not exist in admin.system.users collection. | ||
func setupUserInPostgres(ctx context.Context, logger *zap.SugaredLogger, uri string) error { | ||
sp, err := state.NewProvider("") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
u, err := url.Parse(uri) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
p, err := pool.New(uri, logger.Desugar(), sp) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer p.Close() | ||
|
||
username := u.User.Username() | ||
password, _ := u.User.Password() | ||
|
||
dbPool, err := p.Get(username, password) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer dbPool.Close() | ||
|
||
var pgErr *pgconn.PgError | ||
|
||
q := `CREATE SCHEMA admin` | ||
if _, err = dbPool.Exec(ctx, q); err != nil && (!errors.As(err, &pgErr) || pgErr.Code != pgerrcode.DuplicateSchema) { | ||
return err | ||
} | ||
|
||
if err == nil { | ||
Comment on lines
+300
to
+304
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could simply that by: if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.DuplicateSchema {
err = nil
}
if err != nil {
return err
} |
||
q = `CREATE TABLE admin._ferretdb_database_metadata (_jsonb jsonb)` | ||
if _, err = dbPool.Exec(ctx, q); err != nil { | ||
return err | ||
} | ||
|
||
q = `CREATE UNIQUE INDEX _ferretdb_database_metadata_id_idx ON admin._ferretdb_database_metadata (((_jsonb->'_id')))` | ||
if _, err = dbPool.Exec(ctx, q); err != nil { | ||
return err | ||
} | ||
|
||
q = `CREATE UNIQUE INDEX _ferretdb_database_metadata_table_idx ON admin._ferretdb_database_metadata (((_jsonb->'table')))` | ||
if _, err = dbPool.Exec(ctx, q); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
q = `CREATE TABLE admin.system_users_aff2f7ce (_jsonb jsonb)` | ||
if _, err = dbPool.Exec(ctx, q); err != nil && (!errors.As(err, &pgErr) || pgErr.Code != pgerrcode.DuplicateTable) { | ||
return err | ||
} | ||
|
||
if err == nil { | ||
q = `CREATE UNIQUE INDEX "system_users_aff2f7ce__id__67399184_idx" ON admin.system_users_aff2f7ce (((_jsonb->'_id')))` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yikes!.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes unfortunately this is something needed to be exposed. You know what, let me leave this PR for now and progress with local host exception. |
||
if _, err = dbPool.Exec(ctx, q); err != nil { | ||
return err | ||
} | ||
|
||
q = `INSERT INTO admin._ferretdb_database_metadata (_jsonb) VALUES ($1)` | ||
if _, err = dbPool.Exec(ctx, q, testSystemUsers); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
q = `INSERT INTO admin.system_users_aff2f7ce (_jsonb) VALUES ($1)` | ||
|
||
_, err = dbPool.Exec(ctx, q, testUser) | ||
if err != nil && (!errors.As(err, &pgErr) || pgErr.Code != pgerrcode.UniqueViolation) { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// setupMongodbSecured configures `mongodb_secured` container. | ||
func setupMongodbSecured(ctx context.Context, logger *zap.SugaredLogger) error { | ||
if err := waitForPort(ctx, logger.Named("mongodb_secured"), 47018); err != nil { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
{ | ||
"$s": { | ||
"p": { | ||
"_id": { | ||
"t": "string" | ||
}, | ||
"uuid": { | ||
"t": "string" | ||
}, | ||
"table": { | ||
"t": "string" | ||
}, | ||
"indexes": { | ||
"t": "array", | ||
"i": [ | ||
{ | ||
"t": "object", | ||
"$s": { | ||
"p": { | ||
"pgindex": { | ||
"t": "string" | ||
}, | ||
"name": { | ||
"t": "string" | ||
}, | ||
"key": { | ||
"t": "object", | ||
"$s": { | ||
"p": { | ||
"_id": { | ||
"t": "int" | ||
} | ||
}, | ||
"$k": [ | ||
"_id" | ||
] | ||
} | ||
}, | ||
"unique": { | ||
"t": "bool" | ||
} | ||
}, | ||
"$k": [ | ||
"pgindex", | ||
"name", | ||
"key", | ||
"unique" | ||
] | ||
} | ||
} | ||
] | ||
}, | ||
"cappedSize": { | ||
"t": "long" | ||
}, | ||
"cappedDocs": { | ||
"t": "long" | ||
} | ||
}, | ||
"$k": [ | ||
"_id", | ||
"uuid", | ||
"table", | ||
"indexes", | ||
"cappedSize", | ||
"cappedDocs" | ||
] | ||
}, | ||
"_id": "system.users", | ||
"uuid": "b34c2094-2086-4184-8761-f5692fadb2f2", | ||
"table": "system_users_aff2f7ce", | ||
"indexes": [ | ||
{ | ||
"pgindex": "system_users_aff2f7ce__id__67399184_idx", | ||
"name": "_id_", | ||
"key": { | ||
"_id": 1 | ||
}, | ||
"unique": true | ||
} | ||
], | ||
"cappedSize": 0, | ||
"cappedDocs": 0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For run-secured to work, what other better thing can I do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That target was supposed to be for the "old" authentication that does not need credentials in the PostgreSQL URI. Why are they needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because postgres started on port 5433 is created from
postgres_secured
docker https://github.com/FerretDB/FerretDB/blob/main/docker-compose.yml#L28.Unlike
postgres
docker which hasPOSTGRES_HOST_AUTH_METHOD=trust
,postgres_secured
doesn't havetrust
so username/password is required to connect to it.