From 9571d21871f3223ea110e4228d3a7281b58e5ac6 Mon Sep 17 00:00:00 2001 From: ngcat Date: Thu, 14 Sep 2023 07:30:18 +0800 Subject: [PATCH] add user and password to uri if the uri does not contain user password (#560) * add user and password to uri if the uri does not contain user password * improve compatibility * extract whole `buildURI` logic and write a test * add description for MONGODB_USER and MONGODB_PASSWORD in README.md * fix for lint check * make duplications test code simplified * change properties order to make read easier * add back mongodb:// append logic * clean duplicate mongodb:// append logic * try to fix lint * move third party dependency below to internal dependency --- README.md | 15 +++++++++-- main.go | 22 ++++++++++++--- main_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 00b09a6bf..5cacdc90a 100644 --- a/README.md +++ b/README.md @@ -71,14 +71,25 @@ Connecting user should have sufficient rights to query needed stats: More info about roles in MongoDB [documentation](https://docs.mongodb.com/manual/reference/built-in-roles/#mongodb-authrole-clusterMonitor). #### Example -``` +```sh mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 ``` +#### MongoDB Authentication +You can supply the mongodb user/password direct in the `--mongodb.uri=` like `--mongodb.uri=mongodb://user:pass@127.0.0.1:17001`, you can also supply the mongodb user/password with `--mongodb.user=`, `--mongodb.password=` +but the user and password info will be leaked via `ps` or `top` command, for security issue, you can use `MONGODB_USER` and `MONGODB_PASSWORD` env variable to set user/password for given uri +```sh +MONGODB_USER=XXX MONGODB_PASSWORD=YYY mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 --mongodb.collstats-colls=db1.c1,db2.c2 +# or +export MONGODB_USER=XXX +export MONGODB_PASSWORD=YYY +mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 --mongodb.collstats-colls=db1.c1,db2.c2 +``` + #### Enabling collstats metrics gathering `--mongodb.collstats-colls` receives a list of databases and collections to monitor using collstats. Usage example: `--mongodb.collstats-colls=database1.collection1,database2.collection2` -``` +```sh mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 --mongodb.collstats-colls=db1.c1,db2.c2 ``` #### Enabling compatibility mode. diff --git a/main.go b/main.go index 839031a80..8eb751898 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,8 @@ var ( // GlobalFlags has command line flags to configure the exporter. type GlobalFlags struct { + User string `name:"mongodb.user" help:"monitor user, need clusterMonitor role in admin db and read role in local db" env:"MONGODB_USER" placeholder:"monitorUser"` + Password string `name:"mongodb.password" help:"monitor user password" env:"MONGODB_PASSWORD" placeholder:"monitorPassword"` CollStatsNamespaces string `name:"mongodb.collstats-colls" help:"List of comma separared databases.collections to get $collStats" placeholder:"db1,db2.col2"` IndexStatsCollections string `name:"mongodb.indexstats-colls" help:"List of comma separared databases.collections to get $indexStats" placeholder:"db1.col1,db2.col2"` URI string `name:"mongodb.uri" help:"MongoDB connection URI" env:"MONGODB_URI" placeholder:"mongodb://user:pass@127.0.0.1:27017/admin?ssl=true"` @@ -89,6 +91,21 @@ func main() { e.Run() } +func buildURI(uri string, user string, password string) string { + // IF user@pass not contained in uri AND custom user and pass supplied in arguments + // DO concat a new uri with user and pass arguments value + if !strings.Contains(uri, "@") && user != "" && password != "" { + // trim mongodb:// prefix to handle user and pass logic + uri = strings.TrimPrefix(uri, "mongodb://") + // add user and pass to the uri + uri = fmt.Sprintf("%s:%s@%s", user, password, uri) + } + if !strings.HasPrefix(uri, "mongodb") { + uri = "mongodb://" + uri + } + return uri +} + func buildExporter(opts GlobalFlags) *exporter.Exporter { log := logrus.New() @@ -103,10 +120,7 @@ func buildExporter(opts GlobalFlags) *exporter.Exporter { log.Debugf("Compatible mode: %v", opts.CompatibleMode) - if !strings.HasPrefix(opts.URI, "mongodb") { - log.Debugf("Prepending mongodb:// to the URI") - opts.URI = "mongodb://" + opts.URI - } + opts.URI = buildURI(opts.URI, opts.User, opts.Password) log.Debugf("Connection URI: %s", opts.URI) diff --git a/main_test.go b/main_test.go index 886eb6d0f..bf37e38a0 100644 --- a/main_test.go +++ b/main_test.go @@ -17,6 +17,8 @@ package main import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestBuildExporter(t *testing.T) { @@ -37,3 +39,77 @@ func TestBuildExporter(t *testing.T) { buildExporter(opts) } + +func TestBuildURI(t *testing.T) { + tests := []struct { + situation string + origin string + newUser string + newPassword string + expect string + }{ + { + situation: "uri with prefix and auth, and auth supplied in opt.User/Password", + origin: "mongodb://usr:pwd@127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with prefix and auth, no auth supplied in opt.User/Password", + origin: "mongodb://usr:pwd@127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with no prefix and auth, and auth supplied in opt.User/Password", + origin: "usr:pwd@127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with no prefix and auth, no auth supplied in opt.User/Password", + origin: "usr:pwd@127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with prefix and no auth, and auth supplied in opt.User/Password", + origin: "mongodb://127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://xxx:yyy@127.0.0.1", + }, + { + situation: "uri with prefix and no auth, no auth supplied in opt.User/Password", + origin: "mongodb://127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://127.0.0.1", + }, + { + situation: "uri with no prefix and no auth, and auth supplied in opt.User/Password", + origin: "127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://xxx:yyy@127.0.0.1", + }, + { + situation: "uri with no prefix and no auth, no auth supplied in opt.User/Password", + origin: "127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://127.0.0.1", + }, + } + for _, tc := range tests { + newUri := buildURI(tc.origin, tc.newUser, tc.newPassword) + // t.Logf("Origin: %s", tc.origin) + // t.Logf("Expect: %s", tc.expect) + // t.Logf("Result: %s", newUri) + assert.Equal(t, newUri, tc.expect) + } +}