Skip to content
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

(0.3.0) Postgres plugin: canonicalize & sanitize adress #490

Closed
wants to merge 1 commit into from
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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- **breaking change** Plugin measurements aggregated into a single measurement.
- **breaking change** `jolokia` plugin: must use global tag/drop/pass parameters
for configuration.
- **breaking change** `postgresql` plugin: by default, converts both forms of address
Copy link
Contributor

Choose a reason for hiding this comment

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

is this really a breaking change?

Copy link
Author

Choose a reason for hiding this comment

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

It's definitely not breaking in Telegraf proper; but it could certainly be breaking for tooling built around the data in influx.

but with the field aggregation its a kinda moot point vs 0.2

from config to the key=value format for tag, and drops password. Both behaviors can be
suppressed with the new `verbatim_address` config for that plugin.
- **breaking change** `procstat` plugin has `*cpu*` fields renamed to
`*cpu_time*`
- `twemproxy` plugin: `prefix` option removed.
Expand Down
49 changes: 47 additions & 2 deletions plugins/postgresql/postgresql.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import (
"bytes"
"database/sql"
"fmt"
"regexp"
"strings"

"github.com/influxdb/telegraf/plugins"

_ "github.com/lib/pq"
"github.com/lib/pq"
)

type Postgresql struct {
Address string
Databases []string
OrderedColumns []string

VerbatimAddress bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's just remove the VerbatimAddress option and make it the default.

If people were relying on tag values that contain their password, they really should take the pain and migrate to just the server URL.

sanitizedAddress string
}

var ignoredColumns = map[string]bool{"datid": true, "datname": true, "stats_reset": true}
Expand All @@ -34,6 +38,16 @@ var sampleConfig = `
#
address = "host=localhost user=postgres sslmode=disable"

# Starting in 0.3.0 the default behavior is to convert the above given address to the
Copy link
Contributor

Choose a reason for hiding this comment

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

remove this as a config option

# key value form and, for security, remove the password before using it to tag the
# collected data.
#
# If you are using the URL form and/or have existing tooling matching against a previous
# value, you might want to prevent this transformation / sanitization. Set the following
# to true to leave it as entered for the tag.

# verbatim_address = true

# A list of databases to pull metrics about. If not specified, metrics for all
# databases are gathered.
# databases = ["app_production", "testing"]
Expand Down Expand Up @@ -101,6 +115,27 @@ type scanner interface {
Scan(dest ...interface{}) error
}

var passwordKVMatcher, _ = regexp.Compile("password=\\S+ ?")

func (p *Postgresql) SanitizedAddress() (_ string, err error) {
var canonicalizedAddress string

if p.sanitizedAddress == "" {
if strings.HasPrefix(p.Address, "postgres://") || strings.HasPrefix(p.Address, "postgresql://") {
canonicalizedAddress, err = pq.ParseURL(p.Address)
if err != nil {
return p.sanitizedAddress, err
}
} else {
canonicalizedAddress = p.Address
}

p.sanitizedAddress = passwordKVMatcher.ReplaceAllString(canonicalizedAddress, "")
}

return p.sanitizedAddress, err
}

func (p *Postgresql) accRow(row scanner, acc plugins.Accumulator) error {
var columnVars []interface{}
var dbname bytes.Buffer
Expand Down Expand Up @@ -130,7 +165,17 @@ func (p *Postgresql) accRow(row scanner, acc plugins.Accumulator) error {
dbname.WriteString(string(dbnameChars[i]))
}

tags := map[string]string{"server": p.Address, "db": dbname.String()}
var tagAddress string
if p.VerbatimAddress {
tagAddress = p.Address
} else {
tagAddress, err = p.SanitizedAddress()
if err != nil {
return err
}
}

tags := map[string]string{"server": tagAddress, "db": dbname.String()}

fields := make(map[string]interface{})
for col, val := range columnMap {
Expand Down
98 changes: 98 additions & 0 deletions plugins/postgresql/postgresql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,104 @@ func TestPostgresqlTagsMetricsWithDatabaseName(t *testing.T) {
assert.Equal(t, "postgres", point.Tags["db"])
}

func TestPostgresqlCanonicalizesAndSanitizesURLServerName(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}

p := &Postgresql{
Address: fmt.Sprintf("postgres://postgres:swordfish@%s?sslmode=disable",
testutil.GetLocalHost()),
Databases: []string{"postgres"},
}

var acc testutil.Accumulator

err := p.Gather(&acc)
require.NoError(t, err)

point, ok := acc.Get("postgresql")
require.True(t, ok)

assert.Equal(t,
fmt.Sprintf("host=%s sslmode=disable user=postgres", testutil.GetLocalHost()),
point.Tags["server"])
}

func TestPostgresqlSanitizesKVServerName(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}

p := &Postgresql{
Address: fmt.Sprintf("host=%s user=postgres password=swordfish sslmode=disable",
testutil.GetLocalHost()),
Databases: []string{"postgres"},
}

var acc testutil.Accumulator

err := p.Gather(&acc)
require.NoError(t, err)

point, ok := acc.Get("postgresql")
require.True(t, ok)

assert.Equal(t,
fmt.Sprintf("host=%s user=postgres sslmode=disable", testutil.GetLocalHost()),
point.Tags["server"])
}

func TestPostgresqlMaintainsVerbatimKVServerNameWhenRequested(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}

p := &Postgresql{
Address: fmt.Sprintf("host=%s user=postgres password=swordfish sslmode=disable",
testutil.GetLocalHost()),
VerbatimAddress: true,
Databases: []string{"postgres"},
}

var acc testutil.Accumulator

err := p.Gather(&acc)
require.NoError(t, err)

point, ok := acc.Get("postgresql")
require.True(t, ok)

assert.Equal(t,
fmt.Sprintf("host=%s user=postgres password=swordfish sslmode=disable", testutil.GetLocalHost()),
point.Tags["server"])
}

func TestPostgresqlMaintainsVerbatimURLServerNameWhenRequested(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}

p := &Postgresql{
Address: fmt.Sprintf("postgres://postgres:swordfish@%s?sslmode=disable",
testutil.GetLocalHost()),
VerbatimAddress: true,
Databases: []string{"postgres"},
}

var acc testutil.Accumulator

err := p.Gather(&acc)
require.NoError(t, err)

point, ok := acc.Get("postgresql")
require.True(t, ok)

assert.Equal(t,
fmt.Sprintf("postgres://postgres:swordfish@%s?sslmode=disable", testutil.GetLocalHost()),
point.Tags["server"])
}

func TestPostgresqlDefaultsToAllDatabases(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
Expand Down