@@ -21,12 +21,10 @@ package protosanitizer
2121import (
2222 "encoding/json"
2323 "fmt"
24- "reflect"
25- "strings"
2624
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 "
3028)
3129
3230// StripSecrets returns a wrapper around the original CSI gRPC message
@@ -42,20 +40,11 @@ import (
4240// result to logging functions which may or may not end up serializing
4341// the parameter depending on the current log level.
4442func 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 }
5344}
5445
5546type stripSecrets struct {
56- msg interface {}
57-
58- isSecretField func (field * protobufdescriptor.FieldDescriptorProto ) bool
47+ msg any
5948}
6049
6150func (s * stripSecrets ) String () string {
@@ -66,12 +55,16 @@ func (s *stripSecrets) String() string {
6655 if err != nil {
6756 return fmt .Sprintf ("<<json.Marshal %T: %s>>" , s .msg , err )
6857 }
58+ msg , ok := s .msg .(proto.Message )
59+ if ! ok {
60+ return string (b )
61+ }
6962 if err := json .Unmarshal (b , & parsed ); err != nil {
7063 return fmt .Sprintf ("<<json.Unmarshal %T: %s>>" , s .msg , err )
7164 }
7265
7366 // Now remove secrets from the generic representation of the message.
74- s .strip (parsed , s . msg )
67+ s .strip (parsed , msg . ProtoReflect () )
7568
7669 // Re-encoded the stripped representation and return that.
7770 b , err = json .Marshal (parsed )
@@ -81,13 +74,7 @@ func (s *stripSecrets) String() string {
8174 return string (b )
8275}
8376
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
89- }
90-
77+ func (s * stripSecrets ) strip (parsed interface {}, msg protoreflect.Message ) {
9178 // The corresponding map in the parsed JSON representation.
9279 parsedFields , ok := parsed .(map [string ]interface {})
9380 if ! ok {
@@ -100,77 +87,33 @@ func (s *stripSecrets) strip(parsed interface{}, msg interface{}) {
10087 // on each field where the name matches the field name in the protobuf
10188 // spec (like volume_capabilities). The field.GetJsonName() method returns
10289 // 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 )
90+ msg .Range (func (field protoreflect.FieldDescriptor , v protoreflect.Value ) bool {
91+ name := field .TextName ()
92+ if isCSI1Secret (field ) {
93+ // Overwrite only if already set.
94+ if _ , ok := parsedFields [name ]; ok {
95+ parsedFields [name ] = "***stripped***"
96+ }
97+ } else if field .Kind () == protoreflect .MessageKind && ! field .IsMap () {
98+ entry := parsedFields [name ]
99+ if field .Cardinality () == protoreflect .Repeated {
100+ l := v .List ()
101+ // Array of values, like VolumeCapabilities in CreateVolumeRequest.
102+ for i , entry := range entry .([]interface {}) {
103+ s .strip (entry , l .Get (i ).Message ())
144104 }
105+ } else {
106+ // Single value.
107+ s .strip (entry , v .Message ())
145108 }
146109 }
147- }
110+ return true
111+ })
148112}
149113
150114// isCSI1Secret uses the csi.E_CsiSecret extension from CSI 1.0 to
151115// 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" )
116+ func isCSI1Secret (desc protoreflect.FieldDescriptor ) bool {
117+ ex := proto .GetExtension (desc .Options (), csi .E_CsiSecret )
118+ return ex .(bool )
176119}
0 commit comments