diff --git a/actions/mongodb_explain_action.go b/actions/mongodb_explain_action.go index c5124a9f0..5a360ba01 100644 --- a/actions/mongodb_explain_action.go +++ b/actions/mongodb_explain_action.go @@ -26,8 +26,8 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "github.com/percona/pmm-agent/utils/mongo_fix" "github.com/percona/pmm-agent/utils/templates" ) @@ -68,7 +68,12 @@ func (a *mongodbExplainAction) Run(ctx context.Context) ([]byte, error) { return nil, errors.WithStack(err) } - client, err := mongo.Connect(ctx, options.Client().ApplyURI(dsn)) + opts, err := mongo_fix.ClientOptionsForDSN(dsn) + if err != nil { + return nil, errors.WithStack(err) + } + + client, err := mongo.Connect(ctx, opts) if err != nil { return nil, errors.WithStack(err) } diff --git a/actions/mongodb_query_admincommand_action.go b/actions/mongodb_query_admincommand_action.go index 7be9e23db..6ec71a148 100644 --- a/actions/mongodb_query_admincommand_action.go +++ b/actions/mongodb_query_admincommand_action.go @@ -24,8 +24,8 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "github.com/percona/pmm-agent/utils/mongo_fix" "github.com/percona/pmm-agent/utils/templates" ) @@ -77,7 +77,12 @@ func (a *mongodbQueryAdmincommandAction) Run(ctx context.Context) ([]byte, error return nil, errors.WithStack(err) } - client, err := mongo.Connect(ctx, options.Client().ApplyURI(dsn)) + opts, err := mongo_fix.ClientOptionsForDSN(dsn) + if err != nil { + return nil, errors.WithStack(err) + } + + client, err := mongo.Connect(ctx, opts) if err != nil { return nil, errors.WithStack(err) } diff --git a/agents/mongodb/internal/profiler/collector/collector_test.go b/agents/mongodb/internal/profiler/collector/collector_test.go index 33b7c01d9..6c2a2f5c0 100644 --- a/agents/mongodb/internal/profiler/collector/collector_test.go +++ b/agents/mongodb/internal/profiler/collector/collector_test.go @@ -30,8 +30,9 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" + + "github.com/percona/pmm-agent/utils/mongo_fix" ) const ( @@ -202,8 +203,12 @@ func createSession(dsn string, agentID string) (*mongo.Client, error) { ctx, cancel := context.WithTimeout(context.Background(), MgoTimeoutDialInfo) defer cancel() - opts := options.Client(). - ApplyURI(dsn). + opts, err := mongo_fix.ClientOptionsForDSN(dsn) + if err != nil { + return nil, err + } + + opts = opts. SetDirect(true). SetReadPreference(readpref.Nearest()). SetSocketTimeout(MgoTimeoutSessionSocket). diff --git a/agents/mongodb/internal/profiler/profiler.go b/agents/mongodb/internal/profiler/profiler.go index 6076b5edd..67a3eebc8 100644 --- a/agents/mongodb/internal/profiler/profiler.go +++ b/agents/mongodb/internal/profiler/profiler.go @@ -24,11 +24,11 @@ import ( "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" "github.com/percona/pmm-agent/agents/mongodb/internal/profiler/aggregator" "github.com/percona/pmm-agent/agents/mongodb/internal/profiler/sender" + "github.com/percona/pmm-agent/utils/mongo_fix" ) // New creates new Profiler @@ -192,8 +192,13 @@ func signalReady(ready *sync.Cond) { func createSession(dsn string, agentID string) (*mongo.Client, error) { ctx, cancel := context.WithTimeout(context.Background(), MgoTimeoutDialInfo) defer cancel() - opts := options.Client(). - ApplyURI(dsn). + + opts, err := mongo_fix.ClientOptionsForDSN(dsn) + if err != nil { + return nil, err + } + + opts = opts. SetDirect(true). SetReadPreference(readpref.Nearest()). SetSocketTimeout(MgoTimeoutSessionSocket). diff --git a/connectionchecker/connection_checker.go b/connectionchecker/connection_checker.go index ebee94447..ff70214b2 100644 --- a/connectionchecker/connection_checker.go +++ b/connectionchecker/connection_checker.go @@ -35,10 +35,10 @@ import ( "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" "github.com/percona/pmm-agent/config" "github.com/percona/pmm-agent/tlshelpers" + "github.com/percona/pmm-agent/utils/mongo_fix" "github.com/percona/pmm-agent/utils/templates" ) @@ -162,7 +162,14 @@ func (cc *ConnectionChecker) checkMongoDBConnection(ctx context.Context, dsn str return &res } - client, err := mongo.Connect(ctx, options.Client().ApplyURI(dsn)) + opts, err := mongo_fix.ClientOptionsForDSN(dsn) + if err != nil { + cc.l.Debugf("failed to parse DSN: %s", err) + res.Error = err.Error() + return &res + } + + client, err := mongo.Connect(ctx, opts) if err != nil { cc.l.Debugf("checkMongoDBConnection: failed to Connect: %s", err) res.Error = err.Error() diff --git a/utils/mongo_fix/mongo_fix.go b/utils/mongo_fix/mongo_fix.go new file mode 100644 index 000000000..9cedfb683 --- /dev/null +++ b/utils/mongo_fix/mongo_fix.go @@ -0,0 +1,42 @@ +// pmm-agent +// Copyright 2019 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo_fix + +import ( + "net/url" + + "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// ClientOptionsForDSN applies URI to Client. +func ClientOptionsForDSN(dsn string) (*options.ClientOptions, error) { + clientOptions := options.Client().ApplyURI(dsn) + + // Workaround for PMM-9320 + // if username or password is set, need to replace it with correctly parsed credentials. + parsedDsn, err := url.Parse(dsn) + if err != nil { + return nil, errors.Wrap(err, "cannot parse DSN") + } + username := parsedDsn.User.Username() + password, _ := parsedDsn.User.Password() + if username != "" || password != "" { + clientOptions = clientOptions.SetAuth(options.Credential{Username: username, Password: password}) + } + + return clientOptions, nil +} diff --git a/utils/mongo_fix/mongo_fix_test.go b/utils/mongo_fix/mongo_fix_test.go new file mode 100644 index 000000000..3c4770c99 --- /dev/null +++ b/utils/mongo_fix/mongo_fix_test.go @@ -0,0 +1,48 @@ +package mongo_fix + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClientOptionsForDSN(t *testing.T) { + tests := []struct { + name string + dsn string + expectedUser string + expectedPassword string + }{ + { + name: "Escape username", + dsn: (&url.URL{ + Scheme: "mongo", + Host: "localhost", + Path: "/db", + User: url.UserPassword("user+", "pass"), + }).String(), + expectedUser: "user+", + expectedPassword: "pass", + }, + { + name: "Escape password", + dsn: (&url.URL{ + Scheme: "mongo", + Host: "localhost", + Path: "/db", + User: url.UserPassword("user", "pass+"), + }).String(), + expectedUser: "user", + expectedPassword: "pass+", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ClientOptionsForDSN(tt.dsn) + assert.Nil(t, err) + assert.Equal(t, got.Auth.Username, tt.expectedUser) + assert.Equal(t, got.Auth.Password, tt.expectedPassword) + }) + } +} diff --git a/utils/tests/mongodb.go b/utils/tests/mongodb.go index b807b47eb..54f30eeb9 100644 --- a/utils/tests/mongodb.go +++ b/utils/tests/mongodb.go @@ -25,7 +25,8 @@ import ( "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/percona/pmm-agent/utils/mongo_fix" ) // GetTestMongoDBDSN returns DNS for MongoDB test database. @@ -104,7 +105,12 @@ func GetTestMongoDBReplicatedWithSSLDSN(tb testing.TB, pathToRoot string) (strin func OpenTestMongoDB(tb testing.TB, dsn string) *mongo.Client { tb.Helper() - client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(dsn)) + opts, err := mongo_fix.ClientOptionsForDSN(dsn) + if err != nil { + require.NoError(tb, err) + } + + client, err := mongo.Connect(context.Background(), opts) require.NoError(tb, err) require.NoError(tb, client.Ping(context.Background(), nil))