-
Notifications
You must be signed in to change notification settings - Fork 0
/
safecli.go
228 lines (192 loc) · 6.46 KB
/
safecli.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright 2024 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package safecli
import "strings"
const (
redactedValue = "<****>"
keyValueDelimiter = "="
)
// Redactor defines an interface for handling sensitive value in the way
// that it can be represented both in a plain and redacted text form.
type Redactor interface {
PlainString() string
String() string
}
// createRedactor creates a new Redactor from the given value.
type createRedactor func(value string) Redactor
// Argument stores a key=value pair where the value is subject to redaction.
type Argument struct {
Key string
Value Redactor
}
// SensitiveValue implements Redactor interface for sensitive data.
type SensitiveValue struct {
value string
}
// String returns a redacted string, never the actual value.
func (r SensitiveValue) String() string {
return redactedValue
}
// GoString returns a redacted string, never the actual value for a %#v format too.
func (r SensitiveValue) GoString() string {
return redactedValue
}
// PlainString returns the original sensitive value.
func (r SensitiveValue) PlainString() string {
return r.value
}
// newSensitive returns value as Sensitive.
func newSensitive(value string) Redactor {
return &SensitiveValue{value}
}
// PlainValue implements Redactor interface for non-sensitive data.
type PlainValue struct {
value string
}
// String returns a string as is, without redaction.
func (l PlainValue) String() string {
return l.value
}
// PlainString returns a string as is.
func (l PlainValue) PlainString() string {
return l.value
}
// newPlain returns value as Plain.
func newPlain(value string) Redactor {
return &PlainValue{value}
}
// Builder is used for constructing CLI.
type Builder struct {
// Args stores the command arguments.
Args []Argument
// Formatter defines a function that formats a command argument to the string.
Formatter ArgumentFormatter
}
// append adds a single argument to the builder with a custom redactor.
func (b *Builder) append(key, value string, redactor createRedactor) {
b.Args = append(b.Args, Argument{
Key: key,
Value: redactor(value),
})
}
// appendValues adds values to the builder with a custom redactor.
func (b *Builder) appendValues(values []string, redactor createRedactor) *Builder {
for _, value := range values {
b.append("", value, redactor)
}
return b
}
// appendKeyValuePairs adds key=value pairs to the builder with a custom redactor.
func (b *Builder) appendKeyValuePairs(kvPairs []string, redactor createRedactor) *Builder {
for i := 0; i < len(kvPairs); i += 2 {
key, value := kvPairs[i], ""
if i+1 < len(kvPairs) {
value = kvPairs[i+1]
}
b.append(key, value, redactor)
}
return b
}
// AppendLoggable adds loggable values to the builder.
// These values can be logged and displayed as they do not have sensitive info.
func (b *Builder) AppendLoggable(values ...string) *Builder {
return b.appendValues(values, newPlain)
}
// AppendRedacted adds redacted values to the builder.
// These values are sensitive and should be display in logs as <****>.
func (b *Builder) AppendRedacted(values ...string) *Builder {
return b.appendValues(values, newSensitive)
}
// AppendLoggableKV adds key=value pairs to the builder.
// Key and value are loggable.
func (b *Builder) AppendLoggableKV(kvPairs ...string) *Builder {
return b.appendKeyValuePairs(kvPairs, newPlain)
}
// AppendRedactedKV adds key=value pairs to the builder.
// Key is loggable and value is sensitive.
// The value should be display in logs as <****>.
func (b *Builder) AppendRedactedKV(kvPairs ...string) *Builder {
return b.appendKeyValuePairs(kvPairs, newSensitive)
}
// Append combines the command arguments with the builder.
func (b *Builder) Append(command CommandArguments) *Builder {
b.Args = append(b.Args, command.Arguments()...)
return b
}
// Arguments returns the command arguments.
func (b *Builder) Arguments() []Argument {
return b.Args
}
// Build builds and returns the command.
func (b *Builder) Build() []string {
return formatArguments(b.Formatter, b.Args)
}
// String returns a string representation of the builder with hidden sensitive fields.
func (b *Builder) String() string {
return NewLogger(b).Log()
}
// Logger is used for logging command arguments.
type Logger struct {
// Command stores the Command arguments.
Command CommandArguments
// Formatter defines a function that formats a command argument to the string.
Formatter ArgumentFormatter
}
// Log builds the loggable command string from the command arguments.
func (l *Logger) Log() string {
c := formatArguments(l.Formatter, l.Command.Arguments())
return strings.Join(c, " ")
}
// CommandAppender appends the command arguments to the builder.
type CommandAppender interface {
AppendLoggable(values ...string) *Builder
AppendLoggableKV(kvPairs ...string) *Builder
AppendRedacted(values ...string) *Builder
AppendRedactedKV(kvPairs ...string) *Builder
Append(command CommandArguments) *Builder
}
// CommandBuilder builds and returns the command for execution.
type CommandBuilder interface {
Build() []string
}
// CommandArguments provides an interface for accessing command arguments.
type CommandArguments interface {
Arguments() []Argument
}
// CommandLogger returns a string representation of the command for logging.
type CommandLogger interface {
Log() string
}
// assert that Builder implements CommandBuilder and CommandArguments interfaces
// and Logger implements CommandLogger interface.
var (
_ CommandBuilder = (*Builder)(nil)
_ CommandArguments = (*Builder)(nil)
_ CommandLogger = (*Logger)(nil)
)
// NewBuilder creates a new command builder instance.
// It takes a slice of string values which are appended to the builder.
func NewBuilder(values ...string) *Builder {
b := &Builder{
Formatter: CommandArgumentFormatter,
}
return b.AppendLoggable(values...)
}
// NewLogger creates a new Logger instance.
func NewLogger(command CommandArguments) *Logger {
return &Logger{
Command: command,
Formatter: LogArgumentFormatter,
}
}