@@ -21,12 +21,10 @@ package protosanitizer
21
21
import (
22
22
"encoding/json"
23
23
"fmt"
24
- "reflect"
25
- "strings"
26
24
27
- "github.com/golang/protobuf/descriptor "
28
- "github.com/ golang/protobuf/proto"
29
- protobufdescriptor "github.com/ golang/protobuf/protoc-gen-go/descriptor "
25
+ "github.com/container-storage-interface/spec/lib/go/csi "
26
+ "google. golang.org /protobuf/proto"
27
+ "google. golang.org /protobuf/reflect/protoreflect "
30
28
)
31
29
32
30
// StripSecrets returns a wrapper around the original CSI gRPC message
@@ -42,135 +40,81 @@ import (
42
40
// result to logging functions which may or may not end up serializing
43
41
// the parameter depending on the current log level.
44
42
func StripSecrets (msg interface {}) fmt.Stringer {
45
- return & stripSecrets {msg , isCSI1Secret }
46
- }
47
-
48
- // StripSecretsCSI03 is like StripSecrets, except that it works
49
- // for messages based on CSI 0.3 and older. It does not work
50
- // for CSI 1.0, use StripSecrets for that.
51
- func StripSecretsCSI03 (msg interface {}) fmt.Stringer {
52
- return & stripSecrets {msg , isCSI03Secret }
43
+ return & stripSecrets {msg }
53
44
}
54
45
55
46
type stripSecrets struct {
56
- msg interface {}
57
-
58
- isSecretField func (field * protobufdescriptor.FieldDescriptorProto ) bool
47
+ msg any
59
48
}
60
49
61
50
func (s * stripSecrets ) String () string {
62
- // First convert to a generic representation. That's less efficient
63
- // than using reflect directly, but easier to work with.
64
- var parsed interface {}
65
- b , err := json .Marshal (s .msg )
66
- if err != nil {
67
- return fmt .Sprintf ("<<json.Marshal %T: %s>>" , s .msg , err )
68
- }
69
- if err := json .Unmarshal (b , & parsed ); err != nil {
70
- return fmt .Sprintf ("<<json.Unmarshal %T: %s>>" , s .msg , err )
71
- }
51
+ stripped := s .msg
72
52
73
- // Now remove secrets from the generic representation of the message.
74
- s .strip (parsed , s .msg )
53
+ // also support scalar types like string, int, etc.
54
+ msg , ok := s .msg .(proto.Message )
55
+ if ok {
56
+ stripped = stripMessage (msg .ProtoReflect ())
57
+ }
75
58
76
- // Re-encoded the stripped representation and return that.
77
- b , err = json .Marshal (parsed )
59
+ b , err := json .Marshal (stripped )
78
60
if err != nil {
79
61
return fmt .Sprintf ("<<json.Marshal %T: %s>>" , s .msg , err )
80
62
}
81
63
return string (b )
82
64
}
83
65
84
- func (s * stripSecrets ) strip (parsed interface {}, msg interface {}) {
85
- protobufMsg , ok := msg .(descriptor.Message )
86
- if ! ok {
87
- // Not a protobuf message, so we are done.
88
- return
66
+ func stripSingleValue (field protoreflect.FieldDescriptor , v protoreflect.Value ) any {
67
+ switch field .Kind () {
68
+ case protoreflect .MessageKind :
69
+ return stripMessage (v .Message ())
70
+ case protoreflect .EnumKind :
71
+ return field .Enum ().Values ().ByNumber (v .Enum ()).Name ()
72
+ default :
73
+ return v .Interface ()
89
74
}
75
+ }
90
76
91
- // The corresponding map in the parsed JSON representation.
92
- parsedFields , ok := parsed .(map [string ]interface {})
93
- if ! ok {
94
- // Probably nil.
95
- return
77
+ func stripValue (field protoreflect.FieldDescriptor , v protoreflect.Value ) any {
78
+ if field .IsList () {
79
+ l := v .List ()
80
+ res := make ([]any , l .Len ())
81
+ for i := range l .Len () {
82
+ res [i ] = stripSingleValue (field , l .Get (i ))
83
+ }
84
+ return res
85
+ } else if field .IsMap () {
86
+ m := v .Map ()
87
+ res := make (map [string ]any , m .Len ())
88
+ m .Range (func (mk protoreflect.MapKey , v protoreflect.Value ) bool {
89
+ res [mk .String ()] = stripSingleValue (field .MapValue (), v )
90
+ return true
91
+ })
92
+ return res
93
+ } else {
94
+ return stripSingleValue (field , v )
96
95
}
96
+ }
97
+
98
+ func stripMessage (msg protoreflect.Message ) map [string ]any {
99
+ stripped := make (map [string ]any )
97
100
98
101
// Walk through all fields and replace those with ***stripped*** that
99
- // are marked as secret. This relies on protobuf adding "json:" tags
100
- // on each field where the name matches the field name in the protobuf
101
- // spec (like volume_capabilities). The field.GetJsonName() method returns
102
- // a different name (volumeCapabilities) which we don't use.
103
- _ , md := descriptor .ForMessage (protobufMsg )
104
- fields := md .GetField ()
105
- if fields != nil {
106
- for _ , field := range fields {
107
- if s .isSecretField (field ) {
108
- // Overwrite only if already set.
109
- if _ , ok := parsedFields [field .GetName ()]; ok {
110
- parsedFields [field .GetName ()] = "***stripped***"
111
- }
112
- } else if field .GetType () == protobufdescriptor .FieldDescriptorProto_TYPE_MESSAGE {
113
- // When we get here,
114
- // the type name is something like ".csi.v1.CapacityRange" (leading dot!)
115
- // and looking up "csi.v1.CapacityRange"
116
- // returns the type of a pointer to a pointer
117
- // to CapacityRange. We need a pointer to such
118
- // a value for recursive stripping.
119
- typeName := field .GetTypeName ()
120
- if strings .HasPrefix (typeName , "." ) {
121
- typeName = typeName [1 :]
122
- }
123
- t := proto .MessageType (typeName )
124
- if t == nil || t .Kind () != reflect .Ptr {
125
- // Shouldn't happen, but
126
- // better check anyway instead
127
- // of panicking.
128
- continue
129
- }
130
- v := reflect .New (t .Elem ())
131
-
132
- // Recursively strip the message(s) that
133
- // the field contains.
134
- i := v .Interface ()
135
- entry := parsedFields [field .GetName ()]
136
- if slice , ok := entry .([]interface {}); ok {
137
- // Array of values, like VolumeCapabilities in CreateVolumeRequest.
138
- for _ , entry := range slice {
139
- s .strip (entry , i )
140
- }
141
- } else {
142
- // Single value.
143
- s .strip (entry , i )
144
- }
145
- }
102
+ // are marked as secret.
103
+ msg .Range (func (field protoreflect.FieldDescriptor , v protoreflect.Value ) bool {
104
+ name := field .TextName ()
105
+ if isCSI1Secret (field ) {
106
+ stripped [name ] = "***stripped***"
107
+ } else {
108
+ stripped [name ] = stripValue (field , v )
146
109
}
147
- }
110
+ return true
111
+ })
112
+ return stripped
148
113
}
149
114
150
115
// isCSI1Secret uses the csi.E_CsiSecret extension from CSI 1.0 to
151
116
// determine whether a field contains secrets.
152
- func isCSI1Secret (field * protobufdescriptor.FieldDescriptorProto ) bool {
153
- ex , err := proto .GetExtension (field .Options , e_CsiSecret )
154
- return err == nil && ex != nil && * ex .(* bool )
155
- }
156
-
157
- // Copied from the CSI 1.0 spec (https://github.com/container-storage-interface/spec/blob/37e74064635d27c8e33537c863b37ccb1182d4f8/lib/go/csi/csi.pb.go#L4520-L4527)
158
- // to avoid a package dependency that would prevent usage of this package
159
- // in repos using an older version of the spec.
160
- //
161
- // Future revision of the CSI spec must not change this extensions, otherwise
162
- // they will break filtering in binaries based on the 1.0 version of the spec.
163
- var e_CsiSecret = & proto.ExtensionDesc {
164
- ExtendedType : (* protobufdescriptor .FieldOptions )(nil ),
165
- ExtensionType : (* bool )(nil ),
166
- Field : 1059 ,
167
- Name : "csi.v1.csi_secret" ,
168
- Tag : "varint,1059,opt,name=csi_secret,json=csiSecret" ,
169
- Filename : "github.com/container-storage-interface/spec/csi.proto" ,
170
- }
171
-
172
- // isCSI03Secret relies on the naming convention in CSI <= 0.3
173
- // to determine whether a field contains secrets.
174
- func isCSI03Secret (field * protobufdescriptor.FieldDescriptorProto ) bool {
175
- return strings .HasSuffix (field .GetName (), "_secrets" )
117
+ func isCSI1Secret (desc protoreflect.FieldDescriptor ) bool {
118
+ ex := proto .GetExtension (desc .Options (), csi .E_CsiSecret )
119
+ return ex .(bool )
176
120
}
0 commit comments