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

fix(inputs.postgres*)!: Prevent leaking sensitive data in server tag #14829

Merged
merged 11 commits into from
Mar 1, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
consecutive messages exceeds the timeout.
[#14837](https://github.com/influxdata/telegraf/pull/14828) sets the timeout
to infinite (i.e zero) as this is the expected behavior.
- With correctly sanitizing PostgreSQL addresses ([PR #14829](https://github.com/influxdata/telegraf/pull/14829))
the `server` tag value for a URI-format address might change in case it
contains spaces, backslashes or single-quotes in non-redacted parameters.

## v1.29.5 [2024-02-20]

Expand Down
237 changes: 237 additions & 0 deletions plugins/inputs/postgresql/postgresql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package postgresql

import (
"fmt"
"strings"
"testing"

"github.com/docker/go-connections/nat"
Expand Down Expand Up @@ -316,3 +317,239 @@ func TestPostgresqlDatabaseBlacklistTestIntegration(t *testing.T) {
require.False(t, foundTemplate0)
require.True(t, foundTemplate1)
}

func TestURIParsing(t *testing.T) {
tests := []struct {
name string
uri string
expected string
}{
{
name: "short",
uri: `postgres://localhost`,
expected: "host=localhost",
},
{
name: "with port",
uri: `postgres://localhost:5432`,
expected: "host=localhost port=5432",
},
{
name: "with database",
uri: `postgres://localhost/mydb`,
expected: "dbname=mydb host=localhost",
},
{
name: "with additional parameters",
uri: `postgres://localhost/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5`,
expected: "application_name=pgxtest connect_timeout=5 dbname=mydb host=localhost search_path=myschema",
},
{
name: "with database setting in params",
uri: `postgres://localhost:5432/?database=mydb`,
expected: "database=mydb host=localhost port=5432",
},
{
name: "with authentication",
uri: `postgres://jack:secret@localhost:5432/mydb?sslmode=prefer`,
expected: "dbname=mydb host=localhost password=secret port=5432 sslmode=prefer user=jack",
},
{
name: "with spaces",
uri: `postgres://jack%20hunter:secret@localhost/mydb?application_name=pgx%20test`,
expected: "application_name='pgx test' dbname=mydb host=localhost password=secret user='jack hunter'",
},
{
name: "with equal signs",
uri: `postgres://jack%20hunter:secret@localhost/mydb?application_name=pgx%3Dtest`,
expected: "application_name='pgx=test' dbname=mydb host=localhost password=secret user='jack hunter'",
},
{
name: "multiple hosts",
uri: `postgres://jack:secret@foo:1,bar:2,baz:3/mydb?sslmode=disable`,
expected: "dbname=mydb host=foo,bar,baz password=secret port=1,2,3 sslmode=disable user=jack",
},
{
name: "multiple hosts without ports",
uri: `postgres://jack:secret@foo,bar,baz/mydb?sslmode=disable`,
expected: "dbname=mydb host=foo,bar,baz password=secret sslmode=disable user=jack",
},
}

for _, tt := range tests {
// Key value without spaces around equal sign
t.Run(tt.name, func(t *testing.T) {
actual, err := toKeyValue(tt.uri)
require.NoError(t, err)
require.Equalf(t, tt.expected, actual, "initial: %s", tt.uri)
})
}
}

func TestSanitizeAddressKeyValue(t *testing.T) {
keys := []string{"password", "sslcert", "sslkey", "sslmode", "sslrootcert"}
tests := []struct {
name string
value string
}{
{
name: "simple text",
value: `foo`,
},
{
name: "empty values",
value: `''`,
},
{
name: "space in value",
value: `'foo bar'`,
},
{
name: "equal sign in value",
value: `'foo=bar'`,
},
{
name: "escaped quote",
value: `'foo\'s bar'`,
},
{
name: "escaped quote no space",
value: `\'foobar\'s\'`,
},
{
name: "escaped backslash",
value: `'foo bar\\'`,
},
{
name: "escaped quote and backslash",
value: `'foo\\\'s bar'`,
},
{
name: "two escaped backslashes",
value: `'foo bar\\\\'`,
},
{
name: "multiple inline spaces",
value: "'foo \t bar'",
},
{
name: "leading space",
value: `' foo bar'`,
},
{
name: "trailing space",
value: `'foo bar '`,
},
{
name: "multiple equal signs",
value: `'foo===bar'`,
},
{
name: "leading equal sign",
value: `'=foo bar'`,
},
{
name: "trailing equal sign",
value: `'foo bar='`,
},
{
name: "mix of equal signs and spaces",
value: "'foo = a\t===\tbar'",
},
}

for _, tt := range tests {
// Key value without spaces around equal sign
t.Run(tt.name, func(t *testing.T) {
// Generate the DSN from the given keys and value
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, k+"="+tt.value)
}
dsn := strings.Join(parts, " canary=ok ")

plugin := &Postgresql{
Service: Service{
Address: config.NewSecret([]byte(dsn)),
},
}

expected := strings.Join(make([]string, len(keys)), "canary=ok ")
expected = strings.TrimSpace(expected)
actual, err := plugin.SanitizedAddress()
require.NoError(t, err)
require.Equalf(t, expected, actual, "initial: %s", dsn)
})

// Key value with spaces around equal sign
t.Run("spaced "+tt.name, func(t *testing.T) {
// Generate the DSN from the given keys and value
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, k+" = "+tt.value)
}
dsn := strings.Join(parts, " canary=ok ")

plugin := &Postgresql{
Service: Service{
Address: config.NewSecret([]byte(dsn)),
},
}

expected := strings.Join(make([]string, len(keys)), "canary=ok ")
expected = strings.TrimSpace(expected)
actual, err := plugin.SanitizedAddress()
require.NoError(t, err)
require.Equalf(t, expected, actual, "initial: %s", dsn)
})
}
}

func TestSanitizeAddressURI(t *testing.T) {
keys := []string{"password", "sslcert", "sslkey", "sslmode", "sslrootcert"}
tests := []struct {
name string
value string
}{
{
name: "simple text",
value: `foo`,
},
{
name: "empty values",
value: ``,
},
{
name: "space in value",
value: `foo bar`,
srebhan marked this conversation as resolved.
Show resolved Hide resolved
},
{
name: "equal sign in value",
value: `foo=bar`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Generate the DSN from the given keys and value
value := strings.ReplaceAll(tt.value, "=", "%3D")
value = strings.ReplaceAll(value, " ", "%20")
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, k+"="+value)
}
dsn := "postgresql://user:passwd@localhost:5432/db?" + strings.Join(parts, "&")

plugin := &Postgresql{
Service: Service{
Address: config.NewSecret([]byte(dsn)),
},
}

expected := "dbname=db host=localhost port=5432 user=user"
actual, err := plugin.SanitizedAddress()
require.NoError(t, err)
require.Equalf(t, expected, actual, "initial: %s", dsn)
})
}
}
Loading
Loading