Skip to content

Commit ce81f8f

Browse files
authored
Merge pull request #1630 from Roasbeef/db-conn-fix
tapdb: sanitize db errors before returning
2 parents 25b3a3e + db00e2d commit ce81f8f

File tree

3 files changed

+121
-4
lines changed

3 files changed

+121
-4
lines changed

tapdb/postgres.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) {
9191

9292
rawDb, err := sql.Open("pgx", cfg.DSN(false))
9393
if err != nil {
94-
return nil, err
94+
return nil, MapSQLError(err)
9595
}
9696

9797
maxConns := defaultMaxConns

tapdb/postgres_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//go:build test_db_postgres
2+
3+
package tapdb
4+
5+
import (
6+
"context"
7+
"database/sql"
8+
"testing"
9+
10+
"github.com/lightningnetwork/lnd/macaroons"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
// TestPostgresErrorSanitization tests that we can handle a Postgres connection
15+
// error gracefully, without leaking sensitive information.
16+
func TestPostgresErrorSanitization(t *testing.T) {
17+
t.Parallel()
18+
19+
// We first create a Postgres fixture and a DB that will run the
20+
// migration scripts.
21+
sqlFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime, true)
22+
_, err := NewPostgresStore(sqlFixture.GetConfig())
23+
require.NoError(t, err)
24+
25+
// Now we create a connection config that won't work because of the
26+
// wrong password.
27+
config := sqlFixture.GetConfig()
28+
config.Password = "different"
29+
config.SkipMigrations = true
30+
31+
store, err := NewPostgresStore(config)
32+
require.NoError(t, err)
33+
34+
rksDB := NewTransactionExecutor(store, func(tx *sql.Tx) KeyStore {
35+
return store.WithTx(tx)
36+
})
37+
rks := NewRootKeyStore(rksDB)
38+
39+
ctx := context.Background()
40+
rootKeyCtx := macaroons.ContextWithRootKeyID(ctx, []byte("kek"))
41+
_, _, err = rks.RootKey(rootKeyCtx)
42+
require.Error(t, err)
43+
require.Equal(
44+
t, "unknown postgres error: database connection failed",
45+
err.Error(),
46+
)
47+
}

tapdb/sqlerrors.go

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ func MapSQLError(err error) error {
3232
return parsePostgresError(pqErr)
3333
}
3434

35-
// Return original error if it could not be classified as a database
36-
// specific error.
35+
// As a last step, check if this is a connection error that needs
36+
// sanitization to prevent leaking sensitive information.
37+
err = sanitizeConnectionError(err)
38+
39+
// Return the error (potentially sanitized) if it could not be
40+
// classified as a database specific error.
3741
return err
3842
}
3943

@@ -111,7 +115,8 @@ func parsePostgresError(pqErr *pgconn.PgError) error {
111115
}
112116

113117
default:
114-
return fmt.Errorf("unknown postgres error: %w", pqErr)
118+
return fmt.Errorf("unknown postgres error: %w",
119+
sanitizeConnectionError(pqErr))
115120
}
116121
}
117122

@@ -198,3 +203,68 @@ func IsSchemaError(err error) bool {
198203
var schemaError *ErrSchemaError
199204
return errors.As(err, &schemaError)
200205
}
206+
207+
// ErrDatabaseConnectionError is an error type which represents a database
208+
// connection error with sensitive information sanitized.
209+
type ErrDatabaseConnectionError struct {
210+
DbError error
211+
}
212+
213+
// Unwrap returns the wrapped error.
214+
func (e ErrDatabaseConnectionError) Unwrap() error {
215+
return e.DbError
216+
}
217+
218+
// Error returns a generic error message without revealing connection details.
219+
func (e ErrDatabaseConnectionError) Error() string {
220+
// Return a generic error message that doesn't reveal any connection
221+
// details to prevent information leakage.
222+
return "database connection failed"
223+
}
224+
225+
// isConnectionError checks if an error message contains patterns that indicate
226+
// a database connection error with potentially sensitive information.
227+
func isConnectionError(errStr string) bool {
228+
// List of patterns that indicate connection errors with sensitive info.
229+
patterns := []string{
230+
"failed to connect to",
231+
"dial tcp",
232+
"user=",
233+
"password=",
234+
"host=",
235+
"dbname=",
236+
"sslmode=",
237+
"connection refused",
238+
"no route to host",
239+
"password authentication failed",
240+
}
241+
242+
for _, pattern := range patterns {
243+
if strings.Contains(errStr, pattern) {
244+
return true
245+
}
246+
}
247+
248+
return false
249+
}
250+
251+
// sanitizeConnectionError checks if an error contains database connection
252+
// information and returns a sanitized version if it does.
253+
func sanitizeConnectionError(err error) error {
254+
if err == nil {
255+
return nil
256+
}
257+
258+
// Check if the error message contains connection parameters that could
259+
// leak sensitive information.
260+
if isConnectionError(err.Error()) {
261+
// Log the original error for debugging purposes, but return a
262+
// sanitized version to prevent information leakage.
263+
log.Errorf("Database connection error (sanitized): %v", err)
264+
return &ErrDatabaseConnectionError{
265+
DbError: err,
266+
}
267+
}
268+
269+
return err
270+
}

0 commit comments

Comments
 (0)