-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgosecretfields.go
129 lines (105 loc) · 3.4 KB
/
gosecretfields.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
package gosecretfields
import (
"encoding/json"
"fmt"
)
// Types
// Container for values tagged as secrets
type Secret[T any] struct {
// This is the secret value, it will be always redacted when
// converted into a string through the provided stringer interface
// for the container.
// Wether or not it is redacted when serializing to JSON is determined
// by `CleartextJSON` attribute.
// It can be explicitly accesed through this public attribute.
SecretValue T
// This is the value that replaces the secret value upon redaction.
redactedValue T
// Flag toggling redaction on and off for different kinds of serialization
// so far, only JSON serdes is configurable.
// By assigning references to the same value implementing the `Settings` interface,
// we are able to control a group of `Secret` instances serdes behaviour at the same time.
Settings Settings
}
// Common interface for per-secret instance settings.
type Settings interface {
// Flag controling wether this secret should be JSON serialized with
// its secret or redacted value.
CleartextJSON() bool
}
// Implementation of the `Settings` interface that close down
// changes after initialization.
type ImmutableSettings struct {
enabledClearTextJSON bool
}
func NewImmutableSettings(enabledClearTextJSON bool) ImmutableSettings {
return ImmutableSettings{enabledClearTextJSON}
}
func (is ImmutableSettings) CleartextJSON() bool {
return is.enabledClearTextJSON
}
func (is *ImmutableSettings) CopyAsMutableSettings() MutableSettings {
return MutableSettings{
EnabledClearTextJSON: is.CleartextJSON(),
}
}
// Implementation of the `Settings` interface allowing for in place
// mutation.
type MutableSettings struct {
EnabledClearTextJSON bool
}
func (ms *MutableSettings) CleartextJSON() bool {
return ms.EnabledClearTextJSON
}
func (ms *MutableSettings) Copy() MutableSettings {
return MutableSettings{
EnabledClearTextJSON: ms.CleartextJSON(),
}
}
func (ms *MutableSettings) CopyAsImmutable() ImmutableSettings {
return ImmutableSettings{
enabledClearTextJSON: ms.CleartextJSON(),
}
}
// Shared static values
var defaultSettings = ImmutableSettings{
enabledClearTextJSON: false,
}
func DefaultSettings() Settings {
return &defaultSettings
}
// Factories
// Factory method to wrap any value around a `Secret` container,
// it, the result is a value tagged as secret that won't leak
// on logs or any other string conversion and that might or might
// not be redacted in JSON serializations
func AsSecret[T any](value T, redactedValue ...T) Secret[T] {
var redacted T
if len(redactedValue) > 0 {
redacted = redactedValue[0]
}
return Secret[T]{
SecretValue: value,
redactedValue: redacted,
Settings: DefaultSettings(),
}
}
// JSON serdes
func (s Secret[T]) MarshalJSON() ([]byte, error) {
// Depending on the value of `s.CleartextJSON` flag, JSON serialization of
// Secret fields will result on the redactedValue or the actual secret value JSON representation
// but the container will never show in the JSON structure.
safeValue := s.redactedValue
if s.Settings.CleartextJSON() {
safeValue = s.SecretValue
}
return json.Marshal(safeValue)
}
func (s *Secret[T]) UnmarshalJSON(data []byte) error {
// The fact that a value is tagged as secret is transparent for unmarshalling.
return json.Unmarshal(data, &s.SecretValue)
}
// Stringer interface
func (s Secret[T]) String() string {
return fmt.Sprint(s.redactedValue)
}