This repository has no description
1package models
2
3import (
4 "encoding/base64"
5 "strings"
6)
7
8// SecretMask replaces secret values in strings with "***".
9type SecretMask struct {
10 replacer *strings.Replacer
11 // length of the longest secret. writers keep the last window-1
12 // bytes unflushed so a secret split across writes can still match
13 // whole
14 window int
15}
16
17// NewSecretMask creates a mask for the given secret values.
18// Also registers base64-encoded variants of each secret.
19func NewSecretMask(values []string) *SecretMask {
20 var pairs []string
21 add := func(value string) {
22 if value != "" {
23 pairs = append(pairs, value, "***")
24 }
25 }
26
27 for _, value := range values {
28 if value == "" {
29 continue
30 }
31
32 add(value)
33 // mask each non-empty line of a multiline secret
34 // output may split a secret over multiple log lines...
35 for _, line := range strings.FieldsFunc(value, func(r rune) bool {
36 return r == '\r' || r == '\n'
37 }) {
38 add(line)
39 }
40
41 b64 := base64.StdEncoding.EncodeToString([]byte(value))
42 if b64 != value {
43 add(b64)
44 }
45
46 b64NoPad := strings.TrimRight(b64, "=")
47 if b64NoPad != b64 && b64NoPad != value {
48 add(b64NoPad)
49 }
50 }
51
52 if len(pairs) == 0 {
53 return nil
54 }
55
56 window := 0
57 for i := 0; i < len(pairs); i += 2 {
58 window = max(window, len(pairs[i]))
59 }
60
61 return &SecretMask{
62 replacer: strings.NewReplacer(pairs...),
63 window: window,
64 }
65}
66
67// trailing bytes a streaming caller must keep unflushed so a secret
68// spanning a write boundary still matches
69func (m *SecretMask) Window() int {
70 if m == nil {
71 return 0
72 }
73 if m.window <= 1 {
74 return 0
75 }
76 return m.window - 1
77}
78
79// Mask replaces all registered secret values with "***".
80func (m *SecretMask) Mask(input string) string {
81 if m == nil || m.replacer == nil {
82 return input
83 }
84 return m.replacer.Replace(input)
85}