// Package scrub offers data scrubbing options for protecting sensitive data.
|
|
package scrub
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
// Replacement string.
|
|
Replacement = `*redacted*`
|
|
|
|
// ReplaceChar is used for equal length replacement.
|
|
ReplaceChar = '*'
|
|
)
|
|
|
|
// Common patterns
|
|
const (
|
|
reBase64 = `[0-9A-Za-z./+=,$-]`
|
|
)
|
|
|
|
// Scrubber redacts sensitive data.
|
|
type Scrubber interface {
|
|
// Scrub a string.
|
|
Scrub(string) string
|
|
}
|
|
|
|
// Scrubbers are zero or more Scrubber that act as a single scrubber.
|
|
type Scrubbers []Scrubber
|
|
|
|
// Scrub with all scrubbers, the first scrubber to alter the input will return the scrubbed output.
|
|
func (scrubbers Scrubbers) Scrub(s string) string {
|
|
for _, scrubber := range scrubbers {
|
|
if v := scrubber.Scrub(s); v != s {
|
|
return v
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// All registered scrubbers in safe evaluation order.
|
|
var All = Scrubbers{
|
|
Command,
|
|
CryptHash,
|
|
PEMDHParameters,
|
|
PEMPrivateKey,
|
|
EntropyScrubber{
|
|
Whitespace: []rune(DefaultWhitespace),
|
|
Threshold: DefaultEntropyThreshold,
|
|
},
|
|
}
|
|
|
|
// re is shorthand to compile a regular expression
|
|
func re(pattern string) *regexp.Regexp {
|
|
return regexp.MustCompile(pattern)
|
|
}
|
|
|
|
// ScrubEqualLength redacts match from in, keeping the same length.
|
|
func scrubEqualLength(in, match string) string {
|
|
var (
|
|
l = len(match)
|
|
replace = []byte(strings.Repeat(string(ReplaceChar), l))
|
|
)
|
|
copy(replace, Replacement)
|
|
|
|
// We also keep newline characters from the original string.
|
|
for i, c := range []byte(match) {
|
|
if c == '\n' || c == '\r' || c == '\t' {
|
|
replace[i] = c
|
|
if i+1 < l {
|
|
copy(replace[i+1:], Replacement)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Replace output.
|
|
return strings.Replace(in, match, string(replace), 1)
|
|
}
|
|
|
|
/*
|
|
// Reader acts as an io.Reader.
|
|
func Reader(r io.Reader, scrubbers Scrubbers) io.Reader {
|
|
if len(scrubbers) == 0 {
|
|
return r
|
|
}
|
|
return reader{r, scrubbers}
|
|
}
|
|
|
|
type reader struct {
|
|
r io.Reader
|
|
s Scrubbers
|
|
}
|
|
|
|
// Read from the underlying Reader, then scrub the output buffer.
|
|
func (r reader) Read(p []byte) (n int, err error) {
|
|
if n, err = r.r.Read(p); n > 0 {
|
|
log.Printf("read %d: %q", n, p[:n])
|
|
s := string(p[:n])
|
|
if v := r.s.Scrub(s); v != s {
|
|
m := copy(p, s)
|
|
for i := m; m < n; i++ {
|
|
p[i] = byte(ReplaceChar)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
*/
|
|
|
|
type regexpScrubber struct {
|
|
pattern *regexp.Regexp
|
|
equalLength bool
|
|
}
|
|
|
|
func (r regexpScrubber) Scrub(s string) string {
|
|
matches := r.pattern.FindStringSubmatch(s)
|
|
if len(matches) > 0 {
|
|
for _, match := range matches[1:] {
|
|
if r.equalLength {
|
|
s = scrubEqualLength(s, match)
|
|
} else {
|
|
s = strings.Replace(s, match, Replacement, 1)
|
|
}
|
|
}
|
|
}
|
|
return s
|
|
}
|