Skip to content

Commit ffe5305

Browse files
authored
Merge pull request #184 from huww98/update-proto
cleanups and refactor of protosanitizer
2 parents b3b303b + a4d492b commit ffe5305

File tree

23 files changed

+14172
-19670
lines changed

23 files changed

+14172
-19670
lines changed

connection/connection_test.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,6 @@ const (
5353
serverSock = "server.sock"
5454
)
5555

56-
type identityServer struct{}
57-
58-
func (ids *identityServer) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
59-
return nil, status.Error(codes.Unimplemented, "Unimplemented")
60-
}
61-
62-
func (ids *identityServer) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) {
63-
return nil, status.Error(codes.Unimplemented, "Unimplemented")
64-
}
65-
66-
func (ids *identityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
67-
return nil, status.Error(codes.Unimplemented, "Unimplemented")
68-
}
69-
7056
// startServer creates a gRPC server without any registered services.
7157
// The returned address can be used to connect to it. The cleanup
7258
// function stops it. It can be called multiple times.
@@ -458,7 +444,7 @@ func TestConnectMetrics(t *testing.T) {
458444
cmmServer := metrics.NewCSIMetricsManagerForPlugin("fake.csi.driver.io")
459445
// We have to have a real implementation of the gRPC call, otherwise the metrics
460446
// interceptor is not called. The CSI identity service is used because it's simple.
461-
addr, stopServer := startServer(t, tmp, &identityServer{}, nil, cmmServer)
447+
addr, stopServer := startServer(t, tmp, &csi.UnimplementedIdentityServer{}, nil, cmmServer)
462448
defer stopServer()
463449

464450
cmm := test.cmm
@@ -516,7 +502,7 @@ func TestConnectWithOtelGrpcInterceptorTraces(t *testing.T) {
516502
defer os.RemoveAll(tmp)
517503
// We have to have a real implementation of the gRPC call, otherwise the trace
518504
// interceptor is not called. The CSI identity service is used because it's simple.
519-
addr, stopServer := startServer(t, tmp, &identityServer{}, nil, nil)
505+
addr, stopServer := startServer(t, tmp, &csi.UnimplementedIdentityServer{}, nil, nil)
520506
defer stopServer()
521507

522508
_, ctx := ktesting.NewTestContext(t)

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ module github.com/kubernetes-csi/csi-lib-utils
33
go 1.22.5
44

55
require (
6-
github.com/container-storage-interface/spec v1.9.0
7-
github.com/golang/protobuf v1.5.4
6+
github.com/container-storage-interface/spec v1.10.0
87
github.com/stretchr/testify v1.9.0
98
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0
109
go.opentelemetry.io/otel/trace v1.28.0
11-
golang.org/x/net v0.26.0
1210
google.golang.org/grpc v1.65.0
11+
google.golang.org/protobuf v1.34.2
1312
k8s.io/api v0.31.0
1413
k8s.io/client-go v0.31.0
1514
k8s.io/component-base v0.31.0
@@ -30,6 +29,7 @@ require (
3029
github.com/go-openapi/swag v0.22.4 // indirect
3130
github.com/gogo/protobuf v1.3.2 // indirect
3231
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
32+
github.com/golang/protobuf v1.5.4 // indirect
3333
github.com/google/gnostic-models v0.6.8 // indirect
3434
github.com/google/go-cmp v0.6.0 // indirect
3535
github.com/google/gofuzz v1.2.0 // indirect
@@ -51,13 +51,13 @@ require (
5151
github.com/x448/float16 v0.8.4 // indirect
5252
go.opentelemetry.io/otel v1.28.0 // indirect
5353
go.opentelemetry.io/otel/metric v1.28.0 // indirect
54+
golang.org/x/net v0.26.0 // indirect
5455
golang.org/x/oauth2 v0.21.0 // indirect
5556
golang.org/x/sys v0.22.0 // indirect
5657
golang.org/x/term v0.21.0 // indirect
5758
golang.org/x/text v0.16.0 // indirect
5859
golang.org/x/time v0.3.0 // indirect
5960
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
60-
google.golang.org/protobuf v1.34.2 // indirect
6161
gopkg.in/inf.v0 v0.9.1 // indirect
6262
gopkg.in/yaml.v2 v2.4.0 // indirect
6363
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
99
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
1010
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
1111
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
12-
github.com/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY=
13-
github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0=
12+
github.com/container-storage-interface/spec v1.10.0 h1:YkzWPV39x+ZMTa6Ax2czJLLwpryrQ+dPesB34mrRMXA=
13+
github.com/container-storage-interface/spec v1.10.0/go.mod h1:DtUvaQszPml1YJfIK7c00mlv6/g4wNMLanLgiUbKFRI=
1414
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
1515
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1616
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

protosanitizer/protosanitizer.go

Lines changed: 56 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@ package protosanitizer
2121
import (
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,135 +40,81 @@ import (
4240
// result to logging functions which may or may not end up serializing
4341
// the parameter depending on the current log level.
4442
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}
5344
}
5445

5546
type stripSecrets struct {
56-
msg interface{}
57-
58-
isSecretField func(field *protobufdescriptor.FieldDescriptorProto) bool
47+
msg any
5948
}
6049

6150
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
7252

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+
}
7558

76-
// Re-encoded the stripped representation and return that.
77-
b, err = json.Marshal(parsed)
59+
b, err := json.Marshal(stripped)
7860
if err != nil {
7961
return fmt.Sprintf("<<json.Marshal %T: %s>>", s.msg, err)
8062
}
8163
return string(b)
8264
}
8365

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()
8974
}
75+
}
9076

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)
9695
}
96+
}
97+
98+
func stripMessage(msg protoreflect.Message) map[string]any {
99+
stripped := make(map[string]any)
97100

98101
// 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)
146109
}
147-
}
110+
return true
111+
})
112+
return stripped
148113
}
149114

150115
// isCSI1Secret uses the csi.E_CsiSecret extension from CSI 1.0 to
151116
// 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)
176120
}

0 commit comments

Comments
 (0)