Skip to content

Commit 65ead76

Browse files
asmyasnikovCopilot
andauthored
Masked the sensitive credential data in the connection string (DSN,data source name) from error messages for security reasons. (#1973)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent fbf3502 commit 65ead76

File tree

12 files changed

+196
-27
lines changed

12 files changed

+196
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* Masked the sensitive credential data in the connection string (DSN, data source name) from error messages for security reasons
2+
13
## v3.121.0
24
* Changed internal pprof label to pyroscope supported format
35
* Added `query.ImplicitTxControl()` transaction control (the same as `query.NoTx()` and `query.EmptyTxControl()`). See more about implicit transactions on [ydb.tech](https://ydb.tech/docs/en/concepts/transactions?version=v25.2#implicit)

driver.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
schemeConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme/config"
3232
internalScripting "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting"
3333
scriptingConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting/config"
34+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/secret"
3435
"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
3536
internalTable "github.com/ydb-platform/ydb-go-sdk/v3/internal/table"
3637
tableConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/config"
@@ -290,7 +291,7 @@ func Open(ctx context.Context, dsn string, opts ...Option) (_ *Driver, _ error)
290291
if parser := dsnParsers[parserIdx]; parser != nil {
291292
optsFromParser, err := parser(dsn)
292293
if err != nil {
293-
return nil, xerrors.WithStackTrace(fmt.Errorf("data source name '%s' wrong: %w", dsn, err))
294+
return nil, xerrors.WithStackTrace(fmt.Errorf("data source name '%s' wrong: %w", secret.DSN(dsn), err))
294295
}
295296
opts = append(opts, optsFromParser...)
296297
}

driver_string_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestDriver_String(t *testing.T) {
5252
config.WithSecure(true),
5353
config.WithCredentials(credentials.NewStaticCredentials("user", "password", "")),
5454
)},
55-
s: `Driver{Endpoint:"localhost",Database:"local",Secure:true,Credentials:Static{User:"user",Password:"pas***rd",Token:"****(CRC-32c: 00000000)",From:"github.com/ydb-platform/ydb-go-sdk/v3/credentials.NewStaticCredentials(credentials.go:35)"}}`, //nolint:lll
55+
s: `Driver{Endpoint:"localhost",Database:"local",Secure:true,Credentials:Static{User:"user",Password:"p******d",Token:"****(CRC-32c: 00000000)",From:"github.com/ydb-platform/ydb-go-sdk/v3/credentials.NewStaticCredentials(credentials.go:35)"}}`, //nolint:lll
5656
},
5757
{
5858
name: xtest.CurrentFileLine(),

internal/credentials/errors_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestAccessError(t *testing.T) {
104104
errorString: "something went wrong (" +
105105
"endpoint:\"grps://localhost:2135\"," +
106106
"database:\"/local\"," +
107-
"credentials:\"Static{User:\\\"USER\\\",Password:\\\"SEC**********RD\\\",Token:\\\"****(CRC-32c: 00000000)\\\"}\"" + //nolint:lll
107+
"credentials:\"Static{User:\\\"USER\\\",Password:\\\"S*************D\\\",Token:\\\"****(CRC-32c: 00000000)\\\"}\"" + //nolint:lll
108108
"): test " +
109109
"at `github.com/ydb-platform/ydb-go-sdk/v3/internal/credentials.TestAccessError(errors_test.go:93)`",
110110
},
@@ -123,7 +123,7 @@ func TestAccessError(t *testing.T) {
123123
errorString: "something went wrong (" +
124124
"endpoint:\"grps://localhost:2135\"," +
125125
"database:\"/local\"," +
126-
"credentials:\"Static{User:\\\"USER\\\",Password:\\\"SEC**********RD\\\",Token:\\\"****(CRC-32c: 00000000)\\\",From:\\\"TestAccessError\\\"}\"" + //nolint:lll
126+
"credentials:\"Static{User:\\\"USER\\\",Password:\\\"S*************D\\\",Token:\\\"****(CRC-32c: 00000000)\\\",From:\\\"TestAccessError\\\"}\"" + //nolint:lll
127127
"): test " +
128128
"at `github.com/ydb-platform/ydb-go-sdk/v3/internal/credentials.TestAccessError(errors_test.go:112)`",
129129
},

internal/secret/dsn.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package secret
2+
3+
import (
4+
"net/url"
5+
"strings"
6+
7+
"github.com/ydb-platform/ydb-go-sdk/v3/pkg/xstring"
8+
)
9+
10+
func DSN(dsn string) string {
11+
u, err := url.Parse(dsn)
12+
if err != nil {
13+
return "<invalid DSN>"
14+
}
15+
16+
buffer := xstring.Buffer()
17+
defer buffer.Free()
18+
19+
if u.Scheme != "" {
20+
buffer.WriteString(u.Scheme + "://")
21+
}
22+
23+
if u.User != nil {
24+
buffer.WriteString(u.User.Username())
25+
if password, has := u.User.Password(); has {
26+
buffer.WriteString(":" + Password(password))
27+
}
28+
buffer.WriteString("@")
29+
}
30+
31+
buffer.WriteString(u.Host)
32+
buffer.WriteString(u.Path)
33+
34+
if len(u.RawQuery) > 0 {
35+
buffer.WriteString("?")
36+
37+
params := strings.Split(u.RawQuery, "&")
38+
39+
for i, param := range params {
40+
if i > 0 {
41+
buffer.WriteString("&")
42+
}
43+
paramValue := strings.SplitN(param, "=", 2)
44+
buffer.WriteString(paramValue[0])
45+
if len(paramValue) > 1 {
46+
buffer.WriteString("=")
47+
switch paramValue[0] {
48+
case "token", "password":
49+
buffer.WriteString(Mask(paramValue[1]))
50+
default:
51+
buffer.WriteString(paramValue[1])
52+
}
53+
}
54+
}
55+
}
56+
57+
if u.Fragment != "" {
58+
buffer.WriteString("#" + u.Fragment)
59+
}
60+
61+
return buffer.String()
62+
}

internal/secret/dsn_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package secret
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestDSN(t *testing.T) {
10+
for _, tt := range []struct {
11+
dsn string
12+
exp string
13+
}{
14+
{
15+
dsn: "grpc://192.168.0.%31:2136/",
16+
exp: "<invalid DSN>",
17+
},
18+
{
19+
dsn: "grpc://debuguser:debugpassword@localhost:2136/local1",
20+
exp: "grpc://debuguser:d***********d@localhost:2136/local1",
21+
},
22+
{
23+
dsn: "grpc://host/db?password=pass%3Dword",
24+
exp: "grpc://host/db?password=p*********d",
25+
},
26+
{
27+
dsn: "grpc://host/db?password=pass%26word",
28+
exp: "grpc://host/db?password=p*********d",
29+
},
30+
{
31+
dsn: "grpc://localhost:2136/local1?user=debuguser&password=debugpassword",
32+
exp: "grpc://localhost:2136/local1?user=debuguser&password=d***********d",
33+
},
34+
{
35+
dsn: "grpc://localhost:2136/local1?login=debuguser&password=debugpassword",
36+
exp: "grpc://localhost:2136/local1?login=debuguser&password=d***********d",
37+
},
38+
{
39+
dsn: "grpc://localhost:2136/local1?param1=value1&login=debuguser&param2=value2&password=debugpassword&param2=value3",
40+
exp: "grpc://localhost:2136/local1?param1=value1&login=debuguser&param2=value2&password=d***********d&param2=value3",
41+
},
42+
{
43+
dsn: "grpc://localhost:2136/local1?param1&login=debuguser&param2=value2&password=debugpassword&param2=value3",
44+
exp: "grpc://localhost:2136/local1?param1&login=debuguser&param2=value2&password=d***********d&param2=value3",
45+
},
46+
{
47+
dsn: "grpc://localhost:2136/local1?token=secrettoken123",
48+
exp: "grpc://localhost:2136/local1?token=s************3",
49+
},
50+
} {
51+
t.Run(tt.dsn, func(t *testing.T) {
52+
require.Equal(t, tt.exp, DSN(tt.dsn))
53+
})
54+
}
55+
}

internal/secret/mask.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package secret
2+
3+
func Mask(s string) string {
4+
var (
5+
runes = []rune(s)
6+
startPosition = 1
7+
endPosition = len(runes) - 1
8+
)
9+
10+
if len(runes) < 5 {
11+
startPosition = 0
12+
endPosition = len(runes)
13+
}
14+
15+
for i := startPosition; i < endPosition; i++ {
16+
runes[i] = '*'
17+
}
18+
19+
return string(runes)
20+
}

internal/secret/mask_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package secret
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestMask(t *testing.T) {
10+
for _, tt := range []struct {
11+
s string
12+
exp string
13+
}{
14+
{
15+
s: "test",
16+
exp: "****",
17+
},
18+
{
19+
s: "test-long-password",
20+
exp: "t****************d",
21+
},
22+
{
23+
s: "пароль",
24+
exp: "п****ь",
25+
},
26+
{
27+
s: "пар",
28+
exp: "***",
29+
},
30+
} {
31+
t.Run("", func(t *testing.T) {
32+
require.Equal(t, tt.exp, Mask(tt.s))
33+
})
34+
}
35+
}

internal/secret/password.go

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
11
package secret
22

3-
import (
4-
"github.com/ydb-platform/ydb-go-sdk/v3/pkg/xstring"
5-
)
6-
73
func Password(password string) string {
8-
var (
9-
bytes = []byte(password)
10-
startPosition = 3
11-
endPosition = len(bytes) - 2
12-
)
13-
if startPosition > endPosition {
14-
for i := range bytes {
15-
bytes[i] = '*'
16-
}
17-
} else {
18-
for i := startPosition; i < endPosition; i++ {
19-
bytes[i] = '*'
20-
}
21-
}
22-
23-
return xstring.FromBytes(bytes)
4+
return Mask(password)
245
}

internal/secret/password_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,15 @@ func TestPassword(t *testing.T) {
1717
},
1818
{
1919
password: "test-long-password",
20-
exp: "tes*************rd",
20+
exp: "t****************d",
21+
},
22+
{
23+
password: "пароль",
24+
exp: "п****ь",
25+
},
26+
{
27+
password: "пар",
28+
exp: "***",
2129
},
2230
} {
2331
t.Run("", func(t *testing.T) {

0 commit comments

Comments
 (0)