Skip to content

Commit

Permalink
fix(inputs.postgres*)!: Prevent leaking sensitive data in server tag (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
srebhan authored Mar 1, 2024
1 parent 82e4d8b commit e1994a6
Show file tree
Hide file tree
Showing 3 changed files with 304 additions and 58 deletions.
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`,
},
{
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

0 comments on commit e1994a6

Please sign in to comment.