Skip to content
This repository was archived by the owner on Jun 21, 2022. It is now read-only.
Closed
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
3 changes: 2 additions & 1 deletion models/agent_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,13 +442,14 @@ func (s *Agent) DSN(service *Service, dialTimeout time.Duration, database string
}
switch {
case password != "":
u.User = url.UserPassword(username, password)
u.User = url.UserPassword(username, "__password__")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why just not to use url.UserPassword(username, url.QueryEscape(password)) ?

Copy link
Copy Markdown
Contributor Author

@YaroslavPodorvanov YaroslavPodorvanov Apr 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why just not to use url.UserPassword(username, url.QueryEscape(password))

I'm research that solution too in https://gitlab.com/go-yp/mongodb-escape-password-research/:

SOLUTION SUCCESS TOTAL COMMENT
0 36 38 url.UserPassword(username, password)
1 10 38 url.UserPassword(username, url.QueryEscape(password))
2 35 38 "__plus__"
3 36 38 "__password__"
4 37 38 ":__password__@"
5 1 38

Generate 38 password with specials symbols:

	var symbols = "" +
		";,/?:@&=+$" +
		"-_.!~*'()" +
		"#" +
		" " +
		"%" +
		"[]" +
		"абвгґдеє" +
		"\""

	// one quote symbol ' failed always

	for i, symbol := range symbols {
		password := fmt.Sprintf("example%spassword", string(symbol))

		userPasswords = append(userPasswords, [3]string{
			"tester" + strconv.Itoa(i),
			password,
			"/tester_db",
		})
	}

test solutions:

func ping(username, password, db string, solution int) error {
	u := &url.URL{
		Scheme:   "mongodb",
		Host:     "localhost:27017",
		Path:     db,
		RawQuery: "connectTimeoutMS=2000&serverSelectionTimeoutMS=2000",
	}

	var (
		dsn     string
		setAuth = false
	)

	switch solution {
	// SOLUTION 0
	// SOLUTION NOW EXISTS IN pmm-manager
	// 36 / 38
	case solutionExists:
		u.User = url.UserPassword(username, password)

		dsn = u.String()
	// SOLUTION 1
	// Artem's propose solution https://github.com/percona/pmm-managed/pull/1098#discussion_r861838935
	// 10 / 38
	case solutionArtem:
		u.User = url.UserPassword(username, url.QueryEscape(password))

		dsn = u.String()
	// SOLUTION 2
	// Another solution https://github.com/percona/pmm-managed/pull/1003/files
	// 35 / 38
	case solutionPR:
		const plusPlaceholder = "__plus__" // There is a risk of error, when the password contains this string. Can change it to a more unique string.

		password = strings.ReplaceAll(password, "+", plusPlaceholder)

		u.User = url.UserPassword(username, password)

		dsn = u.String()

		dsn = strings.ReplaceAll(dsn, url.QueryEscape(plusPlaceholder), "%2B")
	// SOLUTION 3
	// 36 / 38
	case solutionMy1:
		u.User = url.UserPassword(username, "__password__")

		dsn = u.String()

		dsn = strings.Replace(dsn, "__password__", url.QueryEscape(password), 1)
	// SOLUTION 4
	// 37 / 38
	case solutionMy2:
		u.User = url.UserPassword(username, "__password__")

		dsn = u.String()

		dsn = strings.Replace(dsn, ":__password__@", ":"+url.QueryEscape(password)+"@", 1)
	// SOLUTION 5
	//  1 / 38
	case solutionMy3:
		dsn = u.String()

		setAuth = true
	}

	ctx := context.Background()

	opts := options.Client().ApplyURI(dsn)
	if setAuth {
		opts = opts.SetAuth(options.Credential{
			Username: username,
			Password: password,
		})
	}

	client, err := mongo.Connect(ctx, opts)
	if err != nil {
		return err
	}
	defer client.Disconnect(ctx) //nolint:errcheck

	if err = client.Ping(ctx, nil); err != nil {
		return err
	}

	return nil
}

So main problem that in one sender side standard Go package "url" use one logic for stringify password to valid URL, in receiver side go.mongodb.org/mongo-driver/mongo use another logic for escape this password from URL.

In standard Go we have:

// String returns the encoded userinfo information in the standard form
// of "username[:password]".
func (u *Userinfo) String() string {
	if u == nil {
		return ""
	}
	s := escape(u.username, encodeUserPassword)
	if u.passwordSet {
		s += ":" + escape(u.password, encodeUserPassword)
	}
	return s
}
// Package url parses URLs and implements query escaping.
package url

escape(u.password, encodeUserPassword)

In go.mongodb.org/mongo-driver/mongo :

p.Password, err = url.QueryUnescape(password)
// Package url parses URLs and implements query escaping.
package url

func QueryUnescape(s string) (string, error) {
	return unescape(s, encodeQueryComponent)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn. Also it seems that we are not alone with this problem: https://kecci.medium.com/handle-mongo-connection-with-percent-encoding-in-go-c8b52b58094f

But what is more important that net/url is not suitable for MongoDB URIs. Maybe we can use something that mongo driver uses instead of net/url here?

Copy link
Copy Markdown
Contributor Author

@YaroslavPodorvanov YaroslavPodorvanov May 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MongoDB driver use "go.mongodb.org/mongo-driver/x/mongo/driver/connstring", but connstring work only with parsing https://pkg.go.dev/go.mongodb.org/mongo-driver/x/mongo/driver/connstring

ConnString.String() not stringify URL

type ConnString struct {
	Original                           string
	// ...
}

func (p *parser) parse(original string) error {
	p.Original = original
	// ...
	return nil
}

func (u *ConnString) String() string {
	return u.Original
}

Maybe we can use something that mongo driver uses instead of net/url here?

No, ConnString cannot do it, and i did search in MongoDB driver by *.go "Password:"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case username != "":
u.User = url.User(username)
}
dsn := u.String()
dsn = strings.ReplaceAll(dsn, url.QueryEscape(tdp.Left), tdp.Left)
dsn = strings.ReplaceAll(dsn, url.QueryEscape(tdp.Right), tdp.Right)
dsn = strings.Replace(dsn, ":__password__@", ":"+url.QueryEscape(password)+"@", 1)
return dsn

case PostgresExporterType, QANPostgreSQLPgStatementsAgentType, QANPostgreSQLPgStatMonitorAgentType:
Expand Down
39 changes: 23 additions & 16 deletions models/agent_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func TestAgent(t *testing.T) {
models.ProxySQLExporterType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?timeout=1s",
models.QANMySQLPerfSchemaAgentType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?clientFoundRows=true&parseTime=true&timeout=1s",
models.QANMySQLSlowlogAgentType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?clientFoundRows=true&parseTime=true&timeout=1s",
models.MongoDBExporterType: "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
models.MongoDBExporterType: "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
models.PostgresExporterType: "postgres://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?connect_timeout=1&sslmode=disable",
} {
t.Run(string(typ), func(t *testing.T) {
Expand All @@ -74,8 +74,15 @@ func TestAgent(t *testing.T) {
t.Run("MongoDBNoDatabase", func(t *testing.T) {
agent.AgentType = models.MongoDBExporterType

assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/", agent.DSN(service, 0, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/", agent.DSN(service, 0, "", nil))
})

t.Run("MongoDBPasswordContainsPlus", func(t *testing.T) {
agent.AgentType = models.MongoDBExporterType
agent.Password = pointer.ToString("s3cur3 p@$$w0r4." + "+" + ":__password__@")

assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.%2B%3A__password__%40@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000", agent.DSN(service, time.Second, "database", nil))
})
})

Expand Down Expand Up @@ -109,8 +116,8 @@ func TestAgent(t *testing.T) {
Socket: pointer.ToString("/var/run/mysqld/mysqld.sock"),
}
for typ, expected := range map[models.AgentType]string{
models.MongoDBExporterType: "mongodb://username:s3cur3%20p%40$$w0r4.@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3%20p%40$$w0r4.@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
models.MongoDBExporterType: "mongodb://username:s3cur3+p%40%24%24w0r4.@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3+p%40%24%24w0r4.@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
} {
t.Run(string(typ), func(t *testing.T) {
agent.AgentType = typ
Expand Down Expand Up @@ -154,8 +161,8 @@ func TestAgent(t *testing.T) {
models.ProxySQLExporterType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?timeout=1s&tls=true",
models.QANMySQLPerfSchemaAgentType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?clientFoundRows=true&parseTime=true&timeout=1s&tls=custom",
models.QANMySQLSlowlogAgentType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?clientFoundRows=true&parseTime=true&timeout=1s&tls=custom",
models.MongoDBExporterType: "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?authMechanism=MONGODB-X509&connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}&tlsCertificateKeyFilePassword=pass",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?authMechanism=MONGODB-X509&connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}&tlsCertificateKeyFilePassword=pass",
models.MongoDBExporterType: "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/database?authMechanism=MONGODB-X509&connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}&tlsCertificateKeyFilePassword=pass",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/database?authMechanism=MONGODB-X509&connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}&tlsCertificateKeyFilePassword=pass",
models.PostgresExporterType: "postgres://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?connect_timeout=1&sslcert={{.TextFiles.certificateFilePlaceholder}}&sslkey={{.TextFiles.certificateKeyFilePlaceholder}}&sslmode=verify-ca&sslrootcert={{.TextFiles.caFilePlaceholder}}",
} {
t.Run(string(typ), func(t *testing.T) {
Expand All @@ -171,8 +178,8 @@ func TestAgent(t *testing.T) {
agent.MongoDBOptions.TLSCertificateKeyFilePassword = ""
agent.MongoDBOptions.AuthenticationMechanism = ""

assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/?ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, 0, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/?ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, 0, "", nil))
expectedFiles := map[string]string{
"caFilePlaceholder": "cert",
"certificateKeyFilePlaceholder": "key",
Expand All @@ -187,8 +194,8 @@ func TestAgent(t *testing.T) {
agent.MongoDBOptions.AuthenticationMechanism = "MONGO-X509"
agent.MongoDBOptions.AuthenticationDatabase = "$external"

assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/?authMechanism=MONGO-X509&authSource=%24external&connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/?authMechanism=MONGO-X509&authSource=%24external&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, 0, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/?authMechanism=MONGO-X509&authSource=%24external&connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/?authMechanism=MONGO-X509&authSource=%24external&ssl=true&tlsCaFile={{.TextFiles.caFilePlaceholder}}&tlsCertificateKeyFile={{.TextFiles.certificateKeyFilePlaceholder}}", agent.DSN(service, 0, "", nil))
expectedFiles := map[string]string{
"caFilePlaceholder": "cert",
"certificateKeyFilePlaceholder": "key",
Expand All @@ -213,8 +220,8 @@ func TestAgent(t *testing.T) {
models.ProxySQLExporterType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?timeout=1s&tls=skip-verify",
models.QANMySQLPerfSchemaAgentType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?clientFoundRows=true&parseTime=true&timeout=1s&tls=skip-verify",
models.QANMySQLSlowlogAgentType: "username:s3cur3 p@$$w0r4.@tcp(1.2.3.4:12345)/database?clientFoundRows=true&parseTime=true&timeout=1s&tls=skip-verify",
models.MongoDBExporterType: "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsInsecure=true",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsInsecure=true",
models.MongoDBExporterType: "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsInsecure=true",
models.QANMongoDBProfilerAgentType: "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/database?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsInsecure=true",
models.PostgresExporterType: "postgres://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/database?connect_timeout=1&sslmode=require",
} {
t.Run(string(typ), func(t *testing.T) {
Expand All @@ -226,8 +233,8 @@ func TestAgent(t *testing.T) {
t.Run("MongoDBNoDatabase", func(t *testing.T) {
agent.AgentType = models.MongoDBExporterType

assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsInsecure=true", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:12345/?ssl=true&tlsInsecure=true", agent.DSN(service, 0, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000&ssl=true&tlsInsecure=true", agent.DSN(service, time.Second, "", nil))
assert.Equal(t, "mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:12345/?ssl=true&tlsInsecure=true", agent.DSN(service, 0, "", nil))
})
})
}
Expand Down
10 changes: 5 additions & 5 deletions services/agents/mongodb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestMongodbExporterConfig225(t *testing.T) {
"--web.listen-address=:{{ .listen_port }}",
},
Env: []string{
"MONGODB_URI=mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"MONGODB_URI=mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"HTTP_AUTH=pmm:agent-password",
},
RedactWords: []string{"s3cur3 p@$$w0r4.", "agent-password"},
Expand Down Expand Up @@ -113,7 +113,7 @@ func TestMongodbExporterConfig226(t *testing.T) {
"--web.listen-address=:{{ .listen_port }}",
},
Env: []string{
"MONGODB_URI=mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"MONGODB_URI=mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"HTTP_AUTH=pmm:agent-password",
},
RedactWords: []string{"s3cur3 p@$$w0r4.", "agent-password"},
Expand Down Expand Up @@ -256,7 +256,7 @@ func TestMongodbExporterConfig(t *testing.T) {
"--web.listen-address=:{{ .listen_port }}",
},
Env: []string{
"MONGODB_URI=mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"MONGODB_URI=mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"HTTP_AUTH=pmm:agent-password",
},
RedactWords: []string{"s3cur3 p@$$w0r4.", "agent-password"},
Expand Down Expand Up @@ -369,7 +369,7 @@ func TestNewMongodbExporterConfig(t *testing.T) {
"--web.listen-address=:{{ .listen_port }}",
},
Env: []string{
"MONGODB_URI=mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"MONGODB_URI=mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"HTTP_AUTH=pmm:agent-id",
},
RedactWords: []string{"s3cur3 p@$$w0r4."},
Expand Down Expand Up @@ -421,7 +421,7 @@ func TestMongodbExporterConfig228_WebConfigAuth(t *testing.T) {
}

expectedEnv := []string{
"MONGODB_URI=mongodb://username:s3cur3%20p%40$$w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
"MONGODB_URI=mongodb://username:s3cur3+p%40%24%24w0r4.@1.2.3.4:27017/?connectTimeoutMS=1000&serverSelectionTimeoutMS=1000",
}

t.Run("Custom_Password", func(t *testing.T) {
Expand Down