Skip to content

Commit f5183c6

Browse files
authored
Merge pull request #198 from replicatedhq/laverya/redaction-reports
redaction reports framework
2 parents 22d397c + 5a2c153 commit f5183c6

File tree

10 files changed

+399
-46
lines changed

10 files changed

+399
-46
lines changed

cmd/troubleshoot/cli/run.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package cli
22

33
import (
4+
"bytes"
45
"crypto/tls"
6+
"encoding/json"
57
"fmt"
68
"io/ioutil"
79
"net/http"
@@ -22,6 +24,7 @@ import (
2224
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
2325
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
2426
"github.com/replicatedhq/troubleshoot/pkg/collect"
27+
"github.com/replicatedhq/troubleshoot/pkg/redact"
2528
"github.com/spf13/viper"
2629
spin "github.com/tj/go-spin"
2730
)
@@ -370,6 +373,33 @@ func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath strin
370373
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
371374
}
372375

376+
// send redaction report
377+
if r.RedactURI != "" {
378+
type PutSupportBundleRedactions struct {
379+
Redactions redact.RedactionList `json:"redactions"`
380+
}
381+
382+
redactBytes, err := json.Marshal(PutSupportBundleRedactions{Redactions: redact.GetRedactionList()})
383+
if err != nil {
384+
return errors.Wrap(err, "get redaction report")
385+
}
386+
387+
req, err := http.NewRequest("PUT", r.RedactURI, bytes.NewReader(redactBytes))
388+
if err != nil {
389+
return errors.Wrap(err, "create redaction report request")
390+
}
391+
req.ContentLength = int64(len(redactBytes))
392+
393+
resp, err := httpClient.Do(req)
394+
if err != nil {
395+
return errors.Wrap(err, "execute redaction request")
396+
}
397+
398+
if resp.StatusCode >= 300 {
399+
return fmt.Errorf("unexpected redaction status code %d", resp.StatusCode)
400+
}
401+
}
402+
373403
return nil
374404
}
375405

pkg/apis/troubleshoot/v1beta1/collector_types.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import (
2121
)
2222

2323
type ResultRequest struct {
24-
URI string `json:"uri" yaml:"uri"`
25-
Method string `json:"method" yaml:"method"`
24+
URI string `json:"uri" yaml:"uri"`
25+
Method string `json:"method" yaml:"method"`
26+
RedactURI string `json:"redactUri" yaml:"redactUri"` // the URI to POST redaction reports to
2627
}
2728

2829
type AfterCollection struct {

pkg/redact/literal.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ import (
99

1010
type literalRedactor struct {
1111
matchString string
12+
filePath string
13+
redactName string
1214
}
1315

14-
func literalString(matchString string) Redactor {
15-
return literalRedactor{matchString: matchString}
16+
func literalString(matchString, path, name string) Redactor {
17+
return literalRedactor{
18+
matchString: matchString,
19+
filePath: path,
20+
redactName: name,
21+
}
1622
}
1723

1824
func (r literalRedactor) Redact(input io.Reader) io.Reader {
@@ -29,18 +35,31 @@ func (r literalRedactor) Redact(input io.Reader) io.Reader {
2935
}()
3036

3137
reader := bufio.NewReader(input)
38+
lineNum := 0
3239
for {
40+
lineNum++
3341
var line string
3442
line, err = readLine(reader)
3543
if err != nil {
3644
return
3745
}
3846

47+
clean := strings.ReplaceAll(line, r.matchString, MASK_TEXT)
48+
3949
// io.WriteString would be nicer, but scanner strips new lines
40-
fmt.Fprintf(writer, "%s\n", strings.ReplaceAll(line, r.matchString, MASK_TEXT))
50+
fmt.Fprintf(writer, "%s\n", clean)
4151
if err != nil {
4252
return
4353
}
54+
55+
if clean != line {
56+
addRedaction(Redaction{
57+
RedactorName: r.redactName,
58+
CharactersRemoved: len(line) - len(clean),
59+
Line: lineNum,
60+
File: r.filePath,
61+
})
62+
}
4463
}
4564
}()
4665
return out

pkg/redact/multi_line.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import (
88
)
99

1010
type MultiLineRedactor struct {
11-
re1 *regexp.Regexp
12-
re2 *regexp.Regexp
13-
maskText string
11+
re1 *regexp.Regexp
12+
re2 *regexp.Regexp
13+
maskText string
14+
filePath string
15+
redactName string
1416
}
1517

16-
func NewMultiLineRedactor(re1, re2, maskText string) (*MultiLineRedactor, error) {
18+
func NewMultiLineRedactor(re1, re2, maskText, path, name string) (*MultiLineRedactor, error) {
1719
compiled1, err := regexp.Compile(re1)
1820
if err != nil {
1921
return nil, err
@@ -22,7 +24,7 @@ func NewMultiLineRedactor(re1, re2, maskText string) (*MultiLineRedactor, error)
2224
if err != nil {
2325
return nil, err
2426
}
25-
return &MultiLineRedactor{re1: compiled1, re2: compiled2, maskText: maskText}, nil
27+
return &MultiLineRedactor{re1: compiled1, re2: compiled2, maskText: maskText, filePath: path, redactName: name}, nil
2628
}
2729

2830
func (r *MultiLineRedactor) Redact(input io.Reader) io.Reader {
@@ -45,7 +47,10 @@ func (r *MultiLineRedactor) Redact(input io.Reader) io.Reader {
4547
}
4648

4749
flushLastLine := false
50+
lineNum := 1
4851
for err == nil {
52+
lineNum++ // the first line that can be redacted is line 2
53+
4954
// If line1 matches re1, then transform line2 using re2
5055
if !r.re1.MatchString(line1) {
5156
fmt.Fprintf(writer, "%s\n", line1)
@@ -63,6 +68,16 @@ func (r *MultiLineRedactor) Redact(input io.Reader) io.Reader {
6368
return
6469
}
6570

71+
// if clean is not equal to line2, a redaction was performed
72+
if clean != line2 {
73+
addRedaction(Redaction{
74+
RedactorName: r.redactName,
75+
CharactersRemoved: len(line2) - len(clean),
76+
Line: lineNum,
77+
File: r.filePath,
78+
})
79+
}
80+
6681
line1, line2, err = getNextTwoLines(reader, nil)
6782
}
6883

pkg/redact/redact.go

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"io/ioutil"
99
"regexp"
10+
"sync"
1011

1112
"github.com/gobwas/glob"
1213
"github.com/pkg/errors"
@@ -17,12 +18,36 @@ const (
1718
MASK_TEXT = "***HIDDEN***"
1819
)
1920

21+
var allRedactions RedactionList
22+
var redactionListMut sync.Mutex
23+
var pendingRedactions sync.WaitGroup
24+
25+
func init() {
26+
allRedactions = RedactionList{
27+
ByRedactor: map[string][]Redaction{},
28+
ByFile: map[string][]Redaction{},
29+
}
30+
}
31+
2032
type Redactor interface {
2133
Redact(input io.Reader) io.Reader
2234
}
2335

36+
// Redactions are indexed both by the file affected and by the name of the redactor
37+
type RedactionList struct {
38+
ByRedactor map[string][]Redaction
39+
ByFile map[string][]Redaction
40+
}
41+
42+
type Redaction struct {
43+
RedactorName string
44+
CharactersRemoved int
45+
Line int
46+
File string
47+
}
48+
2449
func Redact(input []byte, path string, additionalRedactors []*troubleshootv1beta1.Redact) ([]byte, error) {
25-
redactors, err := getRedactors()
50+
redactors, err := getRedactors(path)
2651
if err != nil {
2752
return nil, err
2853
}
@@ -46,9 +71,25 @@ func Redact(input []byte, path string, additionalRedactors []*troubleshootv1beta
4671
return redacted, nil
4772
}
4873

74+
func GetRedactionList() RedactionList {
75+
pendingRedactions.Wait()
76+
redactionListMut.Lock()
77+
defer redactionListMut.Unlock()
78+
return allRedactions
79+
}
80+
81+
func ResetRedactionList() {
82+
redactionListMut.Lock()
83+
defer redactionListMut.Unlock()
84+
allRedactions = RedactionList{
85+
ByRedactor: map[string][]Redaction{},
86+
ByFile: map[string][]Redaction{},
87+
}
88+
}
89+
4990
func buildAdditionalRedactors(path string, redacts []*troubleshootv1beta1.Redact) ([]Redactor, error) {
5091
additionalRedactors := []Redactor{}
51-
for _, redact := range redacts {
92+
for i, redact := range redacts {
5293
if redact == nil {
5394
continue
5495
}
@@ -62,28 +103,30 @@ func buildAdditionalRedactors(path string, redacts []*troubleshootv1beta1.Redact
62103
continue
63104
}
64105

106+
withinRedactNum := 0 // give unique redaction names
107+
65108
for _, re := range redact.Regex {
66-
r, err := NewSingleLineRedactor(re, MASK_TEXT)
109+
r, err := NewSingleLineRedactor(re, MASK_TEXT, path, redactorName(i, withinRedactNum, redact.Name, "regex", ""))
67110
if err != nil {
68111
return nil, errors.Wrapf(err, "redactor %q", re)
69112
}
70113
additionalRedactors = append(additionalRedactors, r)
71114
}
72115

73116
for _, literal := range redact.Values {
74-
additionalRedactors = append(additionalRedactors, literalString(literal))
117+
additionalRedactors = append(additionalRedactors, literalString(literal, path, redactorName(i, withinRedactNum, redact.Name, "literal", "")))
75118
}
76119

77120
for _, re := range redact.MultiLine {
78-
r, err := NewMultiLineRedactor(re.Selector, re.Redactor, MASK_TEXT)
121+
r, err := NewMultiLineRedactor(re.Selector, re.Redactor, MASK_TEXT, path, redactorName(i, withinRedactNum, redact.Name, "multiLine", ""))
79122
if err != nil {
80123
return nil, errors.Wrapf(err, "multiline redactor %+v", re)
81124
}
82125
additionalRedactors = append(additionalRedactors, r)
83126
}
84127

85128
for _, yaml := range redact.Yaml {
86-
r := NewYamlRedactor(yaml)
129+
r := NewYamlRedactor(yaml, path, redactorName(i, withinRedactNum, redact.Name, "yaml", ""))
87130
additionalRedactors = append(additionalRedactors, r)
88131
}
89132
}
@@ -122,7 +165,7 @@ func redactMatchesPath(path string, redact *troubleshootv1beta1.Redact) (bool, e
122165
return false, nil
123166
}
124167

125-
func getRedactors() ([]Redactor, error) {
168+
func getRedactors(path string) ([]Redactor, error) {
126169
// TODO: Make this configurable
127170

128171
// (?i) makes it case insensitive
@@ -159,8 +202,8 @@ func getRedactors() ([]Redactor, error) {
159202
}
160203

161204
redactors := make([]Redactor, 0)
162-
for _, re := range singleLines {
163-
r, err := NewSingleLineRedactor(re, MASK_TEXT)
205+
for i, re := range singleLines {
206+
r, err := NewSingleLineRedactor(re, MASK_TEXT, path, redactorName(-1, i, "", "defaultRegex", re))
164207
if err != nil {
165208
return nil, err // maybe skip broken ones?
166209
}
@@ -201,8 +244,8 @@ func getRedactors() ([]Redactor, error) {
201244
},
202245
}
203246

204-
for _, l := range doubleLines {
205-
r, err := NewMultiLineRedactor(l.line1, l.line2, MASK_TEXT)
247+
for i, l := range doubleLines {
248+
r, err := NewMultiLineRedactor(l.line1, l.line2, MASK_TEXT, path, redactorName(-1, i, "", "defaultMultiLine", l.line1))
206249
if err != nil {
207250
return nil, err // maybe skip broken ones?
208251
}
@@ -247,3 +290,24 @@ func readLine(r *bufio.Reader) (string, error) {
247290
}
248291
return string(completeLine), nil
249292
}
293+
294+
func addRedaction(redaction Redaction) {
295+
pendingRedactions.Add(1)
296+
go func(redaction Redaction) {
297+
redactionListMut.Lock()
298+
defer redactionListMut.Unlock()
299+
defer pendingRedactions.Done()
300+
allRedactions.ByRedactor[redaction.RedactorName] = append(allRedactions.ByRedactor[redaction.RedactorName], redaction)
301+
allRedactions.ByFile[redaction.File] = append(allRedactions.ByFile[redaction.File], redaction)
302+
}(redaction)
303+
}
304+
305+
func redactorName(redactorNum, withinRedactorNum int, redactorName, redactorType, redactorLiteral string) string {
306+
if redactorName != "" {
307+
return fmt.Sprintf("%s-%d", redactorName, withinRedactorNum)
308+
}
309+
if redactorLiteral == "" {
310+
return fmt.Sprintf("unnamed-%d.%d-%s", redactorNum, withinRedactorNum, redactorType)
311+
}
312+
return fmt.Sprintf("%s.%d-%q", redactorType, withinRedactorNum, redactorLiteral)
313+
}

pkg/redact/redact_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1620,11 +1620,14 @@ func Test_Redactors(t *testing.T) {
16201620
}
16211621
]`
16221622

1623+
wantRedactionsLen := 38
1624+
wantRedactionsCount := 25
1625+
16231626
t.Run("test default redactors", func(t *testing.T) {
16241627
scopetest := scopeagent.StartTest(t)
16251628
defer scopetest.End()
16261629
req := require.New(t)
1627-
redactors, err := getRedactors()
1630+
redactors, err := getRedactors("testpath")
16281631
req.NoError(err)
16291632

16301633
nextReader := io.Reader(strings.NewReader(original))
@@ -1636,6 +1639,11 @@ func Test_Redactors(t *testing.T) {
16361639
req.NoError(err)
16371640

16381641
req.JSONEq(expected, string(redacted))
1642+
1643+
actualRedactions := GetRedactionList()
1644+
ResetRedactionList()
1645+
req.Len(actualRedactions.ByFile["testpath"], wantRedactionsLen)
1646+
req.Len(actualRedactions.ByRedactor, wantRedactionsCount)
16391647
})
16401648
}
16411649

0 commit comments

Comments
 (0)