Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Add Redactor interface, UserData type, and redacted log functions * Add global toggle for RedactUserData * Improve benchmark for UserData Enabled/Disabled * Remove incorrect key param in redacted log funcs * Remove unnecessary bench ResetTimer * Add ToUD helper method * Use log redaction in auth package * Expand redacted args slice * Add unit test for redacted log function outputs * Reassign global logger back to original * Disable RedactUserData by default * Add redaction to missed warn * Move log redaction into log package * revert auth * Move log redaction back into base * Moved RedactionLevel into redactor file, made Redact function private again, added test for RedactionLevel
- Loading branch information
1 parent
507c455
commit 870efec
Showing
6 changed files
with
359 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package base | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// Redactor provides an interface for log redaction. | ||
type Redactor interface { | ||
// Redact returns the given string in a redacted form. This may be tagged, | ||
// changed, hashed, or removed completely depending on desired behaviour. | ||
Redact() string | ||
} | ||
|
||
// redact performs an *in-place* redaction on the input slice, and returns it. | ||
// This should only be consumed by logging funcs. E.g. fmt.Printf(fmt, redact(args)) | ||
func redact(args []interface{}) []interface{} { | ||
for i, v := range args { | ||
if r, ok := v.(Redactor); ok { | ||
args[i] = r.Redact() | ||
} | ||
} | ||
return args | ||
} | ||
|
||
type RedactionLevel int | ||
|
||
const ( | ||
RedactNone RedactionLevel = iota | ||
RedactPartial | ||
RedactFull | ||
) | ||
|
||
func SetRedaction(redactionLevel RedactionLevel) { | ||
Logf("Log redaction level: %s", redactionLevel) | ||
switch redactionLevel { | ||
case RedactFull: | ||
RedactUserData = true | ||
case RedactPartial: | ||
RedactUserData = true | ||
case RedactNone: | ||
RedactUserData = false | ||
default: | ||
RedactUserData = false | ||
} | ||
} | ||
|
||
// String returns a lower-case ASCII representation of the log redaction level. | ||
func (l RedactionLevel) String() string { | ||
switch l { | ||
case RedactNone: | ||
return "none" | ||
case RedactPartial: | ||
return "partial" | ||
case RedactFull: | ||
return "full" | ||
default: | ||
return fmt.Sprintf("RedactionLevel(%d)", l) | ||
} | ||
} | ||
|
||
// MarshalText marshals the RedactionLevel to text. | ||
func (l *RedactionLevel) MarshalText() ([]byte, error) { | ||
if l == nil { | ||
return nil, errors.New("can't marshal a nil *RedactionLevel to text") | ||
} | ||
return []byte(l.String()), nil | ||
} | ||
|
||
// UnmarshalText unmarshals text to a RedactionLevel. | ||
func (l *RedactionLevel) UnmarshalText(text []byte) error { | ||
switch strings.ToLower(string(text)) { | ||
case "none": | ||
*l = RedactNone | ||
case "partial": | ||
*l = RedactPartial | ||
case "full": | ||
*l = RedactFull | ||
default: | ||
return fmt.Errorf("unrecognized level: %v", string(text)) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package base | ||
|
||
import ( | ||
"math/big" | ||
"testing" | ||
|
||
assert "github.com/couchbaselabs/go.assert" | ||
) | ||
|
||
func TestRedactHelper(t *testing.T) { | ||
RedactUserData = true | ||
defer func() { RedactUserData = false }() | ||
|
||
ptr := UserData("hello") | ||
|
||
var in = []interface{}{ | ||
UserData("alice"), | ||
&ptr, | ||
"bob", | ||
1234, | ||
big.NewInt(1234), | ||
struct{}{}, | ||
} | ||
|
||
out := redact(in) | ||
|
||
// Since redact performs an in-place redaction, | ||
// we'd expect the input slice to change too. | ||
assert.DeepEquals(t, out, in) | ||
|
||
// Check that ptr wasn't redacted outside of the slice... this could be dangerous! | ||
assert.Equals(t, string(ptr), "hello") | ||
|
||
// Verify that only the types implementing Redactor have changed. | ||
assert.Equals(t, out[0], UserData("alice").Redact()) | ||
assert.Equals(t, out[1], ptr.Redact()) | ||
assert.Equals(t, out[2], "bob") | ||
assert.Equals(t, out[3], 1234) | ||
assert.Equals(t, out[4].(*big.Int).String(), big.NewInt(1234).String()) | ||
assert.Equals(t, out[5], struct{}{}) | ||
} | ||
|
||
func TestSetRedaction(t *testing.T) { | ||
// Hits the default case | ||
SetRedaction(-1) | ||
assert.Equals(t, RedactUserData, false) | ||
|
||
SetRedaction(RedactFull) | ||
assert.Equals(t, RedactUserData, true) | ||
|
||
SetRedaction(RedactPartial) | ||
assert.Equals(t, RedactUserData, true) | ||
|
||
SetRedaction(RedactNone) | ||
assert.Equals(t, RedactUserData, false) | ||
} | ||
|
||
func TestRedactionLevelMarshalText(t *testing.T) { | ||
var level RedactionLevel | ||
level = RedactNone | ||
text, err := level.MarshalText() | ||
assert.Equals(t, err, nil) | ||
assert.Equals(t, string(text), "none") | ||
|
||
level = RedactPartial | ||
text, err = level.MarshalText() | ||
assert.Equals(t, err, nil) | ||
assert.Equals(t, string(text), "partial") | ||
|
||
level = RedactFull | ||
text, err = level.MarshalText() | ||
assert.Equals(t, err, nil) | ||
assert.Equals(t, string(text), "full") | ||
} | ||
|
||
func TestRedactionLevelUnmarshalText(t *testing.T) { | ||
var level RedactionLevel | ||
err := level.UnmarshalText([]byte("none")) | ||
assert.Equals(t, err, nil) | ||
assert.Equals(t, level, RedactNone) | ||
|
||
err = level.UnmarshalText([]byte("partial")) | ||
assert.Equals(t, err, nil) | ||
assert.Equals(t, level, RedactPartial) | ||
|
||
err = level.UnmarshalText([]byte("full")) | ||
assert.Equals(t, err, nil) | ||
assert.Equals(t, level, RedactFull) | ||
|
||
err = level.UnmarshalText([]byte("asdf")) | ||
assert.Equals(t, err.Error(), "unrecognized level: asdf") | ||
} | ||
|
||
func BenchmarkRedactHelper(b *testing.B) { | ||
RedactUserData = true | ||
defer func() { RedactUserData = false }() | ||
|
||
var data = []interface{}{ | ||
UserData("alice"), | ||
"bob", | ||
1234, | ||
big.NewInt(1234), | ||
struct{}{}, | ||
} | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
redact(data) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package base | ||
|
||
import "fmt" | ||
|
||
const ( | ||
userDataPrefix = "<ud>" | ||
userDataSuffix = "</ud>" | ||
) | ||
|
||
// RedactUserData is a global toggle for user data redaction. | ||
var RedactUserData = false | ||
|
||
// UserData is a type which implements the Redactor interface for logging purposes of user data. | ||
// | ||
// User data is data that is stored into Couchbase by the application user account: | ||
// - Key and value pairs in JSON documents, or the key exclusively | ||
// - Application/Admin usernames that identify the human person | ||
// - Query statements included in the log file collected by support that leak the document fields (Select floor_price from stock). | ||
// - Names and email addresses asked during product registration and alerting | ||
// - Usernames | ||
// - Document xattrs | ||
type UserData string | ||
|
||
// Redact tags the string with UserData tags for post-processing. | ||
func (ud UserData) Redact() string { | ||
if !RedactUserData { | ||
return string(ud) | ||
} | ||
return userDataPrefix + string(ud) + userDataSuffix | ||
} | ||
|
||
// Compile-time interface check. | ||
var _ Redactor = UserData("") | ||
|
||
// UD returns a UserData type for any given value. | ||
func UD(i interface{}) UserData { | ||
switch v := i.(type) { | ||
case string: | ||
return UserData(v) | ||
case fmt.Stringer: | ||
return UserData(v.String()) | ||
default: | ||
// Fall back to a slower but safe way of getting a string from any type. | ||
return UserData(fmt.Sprintf("%v", v)) | ||
} | ||
} |
Oops, something went wrong.