Skip to content

Commit cc1a524

Browse files
author
Anil Ambati
committed
[FAB-6247] Sanitize debug messages
This is a back port of changeset https://gerrit.hyperledger.org/r/c/13659/ to v1.0.2 Sanitize debug messages to filter out any sensitive information and updated password test for DB/LDAP Change-Id: I7eefe85f3268c9b18922585dc09df5e3f41e5470 Signed-off-by: Anil Ambati <aambati@us.ibm.com>
1 parent 4c9f3d9 commit cc1a524

File tree

6 files changed

+206
-18
lines changed

6 files changed

+206
-18
lines changed

lib/ca.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ func (ca *CA) initDB() error {
539539
return err
540540
}
541541
}
542-
log.Infof("Initialized %s database at %s", db.Type, db.Datasource)
542+
log.Infof("Initialized %s database", db.Type)
543543
return nil
544544
}
545545

lib/ca_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package lib
1717

1818
import (
1919
"crypto/x509"
20+
"fmt"
2021
"io/ioutil"
2122
"testing"
2223

@@ -179,3 +180,36 @@ func testValidMatchingKeys(cert *x509.Certificate, t *testing.T) {
179180
t.Error("Should have failed, public key and private key do not match")
180181
}
181182
}
183+
184+
// Tests String method of CAConfigDB
185+
func TestCAConfigDBStringer(t *testing.T) {
186+
dbconfig := CAConfigDB{
187+
Type: "postgres",
188+
Datasource: "dbname=mypostgres host=127.0.0.1 port=8888 user=admin password=admin sslmode=disable",
189+
}
190+
str := fmt.Sprintf("%+v", dbconfig) // String method of CAConfigDB is called here
191+
t.Logf("Stringified postgres CAConfigDB: %s", str)
192+
assert.Contains(t, str, "user=****", "Username is not masked in the datasource URL")
193+
assert.Contains(t, str, "password=****", "Password is not masked in the datasource URL")
194+
195+
dbconfig.Datasource = "dbname=mypostgres host=127.0.0.1 port=8888 password=admin sslmode=disable user=admin"
196+
str = fmt.Sprintf("%+v", dbconfig) // String method of CAConfigDB is called here
197+
t.Logf("Stringified postgres CAConfigDB: %s", str)
198+
assert.Contains(t, str, "user=****", "Username is not masked in the datasource URL")
199+
assert.Contains(t, str, "password=****", "Password is not masked in the datasource URL")
200+
201+
dbconfig.Datasource = "dbname=cadb password=adminpwd host=127.0.0.1 port=8888 user=cadb sslmode=disable"
202+
str = fmt.Sprintf("%+v", dbconfig) // String method of CAConfigDB is called here
203+
t.Logf("Stringified postgres CAConfigDB: %s", str)
204+
assert.Contains(t, str, "user=****", "Username is not masked in the datasource URL")
205+
assert.Contains(t, str, "password=****", "Password is not masked in the datasource URL")
206+
207+
dbconfig = CAConfigDB{
208+
Type: "mysql",
209+
Datasource: "root:rootpw@tcp(localhost:8888)/mysqldb?parseTime=true",
210+
}
211+
str = fmt.Sprintf("%+v", dbconfig)
212+
t.Logf("Stringified mysql CAConfigDB: %s", str)
213+
assert.NotContains(t, str, "root", "Username is not masked in the datasource URL")
214+
assert.NotContains(t, str, "rootpw", "Password is not masked in the datasource URL")
215+
}

lib/caconfig.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ limitations under the License.
1717
package lib
1818

1919
import (
20+
"regexp"
21+
"strings"
22+
2023
"github.com/cloudflare/cfssl/config"
2124
"github.com/hyperledger/fabric-ca/api"
2225
"github.com/hyperledger/fabric-ca/lib/ldap"
@@ -60,6 +63,10 @@ csr:
6063
`
6164
)
6265

66+
var (
67+
dbURLRegex = regexp.MustCompile("Datasource:\\s*(\\S+):(\\S+)@|Datasource:.*\\s(user=\\S+).*\\s(password=\\S+)|Datasource:.*\\s(password=\\S+).*\\s(user=\\S+)")
68+
)
69+
6370
// CAConfig is the CA instance's config
6471
// The tags are recognized by the RegisterFlags function in fabric-ca/lib/util.go
6572
// and are as follows:
@@ -97,6 +104,35 @@ type CAConfigDB struct {
97104
TLS tls.ClientTLSConfig
98105
}
99106

107+
// Implements Stringer interface for CAConfigDB
108+
// Calls util.StructToString to convert the CAConfigDB struct to
109+
// string and masks the password from the database URL. Returns
110+
// resulting string.
111+
func (c CAConfigDB) String() string {
112+
str := util.StructToString(&c)
113+
matches := dbURLRegex.FindStringSubmatch(str)
114+
115+
// If there is a match, there should be three entries: 1 for
116+
// the match and 6 for submatches (see dbURLRegex regular expression)
117+
if len(matches) == 7 {
118+
matchIdxs := dbURLRegex.FindStringSubmatchIndex(str)
119+
substr := str[matchIdxs[0]:matchIdxs[1]]
120+
for idx := 1; idx < len(matches); idx++ {
121+
if matches[idx] != "" {
122+
if strings.Index(matches[idx], "user=") == 0 {
123+
substr = strings.Replace(substr, matches[idx], "user=****", 1)
124+
} else if strings.Index(matches[idx], "password=") == 0 {
125+
substr = strings.Replace(substr, matches[idx], "password=****", 1)
126+
} else {
127+
substr = strings.Replace(substr, matches[idx], "****", 1)
128+
}
129+
}
130+
}
131+
str = str[:matchIdxs[0]] + substr + str[matchIdxs[1]:len(str)]
132+
}
133+
return str
134+
}
135+
100136
// CAConfigRegistry is the registry part of the server's config
101137
type CAConfigRegistry struct {
102138
MaxEnrollments int `def:"-1" help:"Maximum number of enrollments; valid if LDAP not enabled"`
@@ -105,7 +141,7 @@ type CAConfigRegistry struct {
105141

106142
// CAConfigIdentity is identity information in the server's config
107143
type CAConfigIdentity struct {
108-
Name string
144+
Name string `secret:"password"`
109145
Pass string `secret:"password"`
110146
Type string
111147
Affiliation string

lib/ldap/client.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@ import (
2121
"fmt"
2222
"net"
2323
"net/url"
24+
"regexp"
2425
"strconv"
2526
"strings"
2627

2728
"github.com/cloudflare/cfssl/log"
2829
"github.com/hyperledger/fabric-ca/lib/spi"
2930
ctls "github.com/hyperledger/fabric-ca/lib/tls"
31+
"github.com/hyperledger/fabric-ca/util"
3032
"github.com/hyperledger/fabric/bccsp"
3133
ldap "gopkg.in/ldap.v2"
3234
)
3335

3436
var (
3537
dnAttr = []string{"dn"}
3638
errNotSupported = errors.New("Not supported")
39+
ldapURLRegex = regexp.MustCompile("ldaps*://(\\S+):(\\S+)@")
3740
)
3841

3942
// Config is the configuration object for this LDAP client
@@ -45,6 +48,28 @@ type Config struct {
4548
TLS ctls.ClientTLSConfig
4649
}
4750

51+
// Implements Stringer interface for ldap.Config
52+
// Calls util.StructToString to convert the Config struct to
53+
// string and masks the password from the ldap URL. Returns
54+
// resulting string.
55+
func (c Config) String() string {
56+
str := util.StructToString(&c)
57+
matches := ldapURLRegex.FindStringSubmatch(str)
58+
// If there is a match, there should be two entries: 1 for
59+
// the match and 2 for submatches
60+
if len(matches) == 3 {
61+
matchIdxs := ldapURLRegex.FindStringSubmatchIndex(str)
62+
substr := str[matchIdxs[0]:matchIdxs[1]]
63+
for idx := 1; idx < len(matches); idx++ {
64+
if matches[idx] != "" {
65+
substr = strings.Replace(substr, matches[idx], "****", 1)
66+
}
67+
}
68+
str = str[:matchIdxs[0]] + substr + str[matchIdxs[1]:len(str)]
69+
}
70+
return str
71+
}
72+
4873
// NewClient creates an LDAP client
4974
func NewClient(cfg *Config, csp bccsp.BCCSP) (*Client, error) {
5075
log.Debugf("Creating new LDAP client for %+v", cfg)

lib/ldap/client_test.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package ldap
1919
import (
2020
"fmt"
2121
"testing"
22+
23+
"github.com/stretchr/testify/assert"
2224
)
2325

2426
func TestLDAP(t *testing.T) {
@@ -40,7 +42,7 @@ func testLDAP(proto string, port int, t *testing.T) {
4042
host := "localhost"
4143
base := "dc=example,dc=org"
4244
url := fmt.Sprintf("%s://%s:%s@%s:%d/%s", proto, dn, pwd, host, port, base)
43-
c, err := NewClient(&Config{URL: url})
45+
c, err := NewClient(&Config{URL: url}, nil)
4446
if err != nil {
4547
t.Errorf("ldap.NewClient failure: %s", err)
4648
return
@@ -50,15 +52,15 @@ func testLDAP(proto string, port int, t *testing.T) {
5052
t.Errorf("ldap.Client.GetUser failure: %s", err)
5153
return
5254
}
53-
err = user.Login("jsmithpw")
55+
err = user.Login("jsmithpw", -1)
5456
if err != nil {
5557
t.Errorf("ldap.User.Login failure: %s", err)
5658
}
5759
path := user.GetAffiliationPath()
5860
if path == nil {
5961
t.Error("ldap.User.GetAffiliationPath is nil")
6062
}
61-
err = user.Login("bogus")
63+
err = user.Login("bogus", -1)
6264
if err == nil {
6365
t.Errorf("ldap.User.Login passed but should have failed")
6466
}
@@ -70,19 +72,19 @@ func testLDAP(proto string, port int, t *testing.T) {
7072
}
7173

7274
func testLDAPNegative(t *testing.T) {
73-
_, err := NewClient(nil)
75+
_, err := NewClient(nil, nil)
7476
if err == nil {
7577
t.Errorf("ldap.NewClient(nil) passed but should have failed")
7678
}
77-
_, err = NewClient(&Config{URL: "bogus"})
79+
_, err = NewClient(&Config{URL: "bogus"}, nil)
7880
if err == nil {
7981
t.Errorf("ldap.NewClient(bogus) passed but should have failed")
8082
}
81-
_, err = NewClient(&Config{URL: "ldaps://localhost"})
83+
_, err = NewClient(&Config{URL: "ldaps://localhost"}, nil)
8284
if err != nil {
8385
t.Errorf("ldap.NewClient(ldaps) failed: %s", err)
8486
}
85-
_, err = NewClient(&Config{URL: "ldap://localhost:badport"})
87+
_, err = NewClient(&Config{URL: "ldap://localhost:badport"}, nil)
8688
if err == nil {
8789
t.Errorf("ldap.NewClient(badport) passed but should have failed")
8890
}
@@ -96,7 +98,7 @@ func TestLDAPTLS(t *testing.T) {
9698
base := "dc=example,dc=org"
9799
port := 10636
98100
url := fmt.Sprintf("%s://%s:%s@%s:%d/%s", proto, dn, pwd, host, port, base)
99-
c, err := NewClient(&Config{URL: url})
101+
c, err := NewClient(&Config{URL: url}, nil)
100102
if err != nil {
101103
t.Errorf("ldap.NewClient failure: %s", err)
102104
return
@@ -109,15 +111,15 @@ func TestLDAPTLS(t *testing.T) {
109111
t.Errorf("ldap.Client.GetUser failure: %s", err)
110112
return
111113
}
112-
err = user.Login("jsmithpw")
114+
err = user.Login("jsmithpw", -1)
113115
if err != nil {
114116
t.Errorf("ldap.User.Login failure: %s", err)
115117
}
116118
path := user.GetAffiliationPath()
117119
if path == nil {
118120
t.Error("ldap.User.GetAffiliationPath is nil")
119121
}
120-
err = user.Login("bogus")
122+
err = user.Login("bogus", -1)
121123
if err == nil {
122124
t.Errorf("ldap.User.Login passed but should have failed")
123125
}
@@ -127,3 +129,28 @@ func TestLDAPTLS(t *testing.T) {
127129
}
128130
t.Logf("email for user 'jsmith' is %s", email)
129131
}
132+
133+
// Tests String method of ldap.Config
134+
func TestLDAPConfigStringer(t *testing.T) {
135+
ldapConfig := Config{
136+
Enabled: true,
137+
URL: "ldap://admin:adminpwd@localhost:8888/users",
138+
UserFilter: "(uid=%s)",
139+
GroupFilter: "(memberUid=%s)",
140+
}
141+
str := fmt.Sprintf("%+v", ldapConfig) // String method of Config is called here
142+
t.Logf("Stringified LDAP Config: %s", str)
143+
assert.NotContains(t, str, "admin", "Username is not masked in the ldap URL")
144+
assert.NotContains(t, str, "adminpwd", "Password is not masked in the ldap URL")
145+
146+
ldapConfig = Config{
147+
Enabled: true,
148+
URL: "ldaps://admin:adminpwd@localhost:8888/users",
149+
UserFilter: "(uid=%s)",
150+
GroupFilter: "(memberUid=%s)",
151+
}
152+
str = fmt.Sprintf("%+v", ldapConfig)
153+
t.Logf("Stringified LDAP Config: %s", str)
154+
assert.NotContains(t, str, "admin", "Username is not masked in the ldap URL")
155+
assert.NotContains(t, str, "adminpwd", "Password is not masked in the ldap URL")
156+
}

scripts/fvt/passwordsInLog_test.sh

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,80 @@
55
# SPDX-License-Identifier: Apache-2.0
66
#
77

8+
function checkPasswd() {
9+
local pswd="$1"
10+
local Type="$2"
11+
: ${Type:="user"}
12+
13+
set -f
14+
# Extract password value(s) from logfile
15+
case "$Type" in
16+
user) passwd=$(egrep -o "Pass:[^[:space:]]+" $LOGFILE| awk -F':' '{print $2}') ;;
17+
ldap) passwd=$(egrep -io "ldap.*@" $LOGFILE| awk -v FS=[:@] '{print $(NF-1)}') ;;
18+
mysql) passwd=$(egrep -o "[a-z0-9*]+@tcp" $LOGFILE| awk -v FS=@ '{print $(NF-1)}') ;;
19+
postgres) passwd=$(egrep -o "password=[^ ]+ " $LOGFILE| awk -F '=' '{print $2}') ;;
20+
esac
21+
22+
# Fail if password is empty
23+
if [[ -z "$passwd" ]] ; then
24+
ErrorMsg "Unable to extract password value(s)"
25+
fi
26+
27+
# Fail if password matches anything other than '*'
28+
for p in $passwd; do
29+
if ! [[ "$p" =~ \*+ ]]; then
30+
ErrorMsg "Passwords were not masked in the log"
31+
fi
32+
done
33+
34+
# ensure any string passed in doesn't appear anywhere in logfile
35+
if [[ -n "$pswd" ]]; then
36+
grep "$pswd" "$LOGFILE" && ErrorMsg "$pswd was not masked in the log"
37+
fi
38+
set +f
39+
}
40+
41+
function passWordSub() {
42+
sed -i "/datasource:/ s/\(password=\)[[:alnum:]]\+\(.*\)/\1$PSWD\2/
43+
s/dc=com:$LDAP_PASSWD/dc=com:$PSWD/
44+
s/datasource:\(.*\)mysql@/datasource:\1$PSWD@/" $TESTDIR/runFabricCaFvt.yaml
45+
}
46+
847
RC=0
48+
TESTCASE="passwordsInLog"
49+
TESTDIR="/tmp/$TESTCASE"
50+
mkdir -p $TESTDIR
51+
952
FABRIC_CA="$GOPATH/src/github.com/hyperledger/fabric-ca"
1053
SCRIPTDIR="$FABRIC_CA/scripts/fvt"
1154
. $SCRIPTDIR/fabric-ca_utils
12-
fabric-ca-server init -b administrator:administratorpw -d &> /tmp/log.txt
13-
grep "administratorpw" /tmp/log.txt &> /dev/null
14-
if [ $? == 0 ]; then
15-
ErrorMsg "Passwords were not masked in the log"
16-
fi
55+
56+
export CA_CFG_PATH="$TESTDIR"
57+
export FABRIC_CA_SERVER_HOME="$TESTDIR"
58+
LOGFILE=$FABRIC_CA_SERVER_HOME/log.txt
59+
60+
USER=administrator
61+
PSWD=thisIs_aLongUniquePasswordWith_aMinisculePossibilityOfBeingDuplicated
62+
63+
# Test using bootstrap ID
64+
fabric-ca-server init -b $USER:$PSWD -d 2>&1 | tee $LOGFILE
65+
test ${PIPESTATUS[0]} -eq 0 && checkPasswd "$PSWD" || ErrorMsg "Init of CA failed"
66+
67+
# Test using multiple IDs from pre-supplied config file
68+
$SCRIPTDIR/fabric-ca_setup.sh -R; mkdir -p $TESTDIR
69+
$SCRIPTDIR/fabric-ca_setup.sh -I -X -n1 -D 2>&1 | tee $LOGFILE
70+
test ${PIPESTATUS[0]} -eq 0 && checkPasswd "$PSWD" || ErrorMsg "Init of CA failed"
71+
72+
for server in ldap mysql postgres; do
73+
$SCRIPTDIR/fabric-ca_setup.sh -R; mkdir -p $TESTDIR
74+
case $server in
75+
ldap) $SCRIPTDIR/fabric-ca_setup.sh -a -I -D > $LOGFILE 2>&1 ;;
76+
*) $SCRIPTDIR/fabric-ca_setup.sh -I -D -d $server 2>&1 > $LOGFILE ;;
77+
esac
78+
passWordSub
79+
$SCRIPTDIR/fabric-ca_setup.sh -S >> $LOGFILE 2>&1
80+
checkPasswd "$PSWD" $server
81+
done
82+
1783
CleanUp $RC
18-
exit $RC
84+
exit $RC

0 commit comments

Comments
 (0)