Skip to content

Commit 278086b

Browse files
committed
protosanitizer: recurse into all fields
It now works with all kinds of nested fields, and don't require json.Marshal() twice. The output also looks better for oneof and enum.
1 parent 5a88df5 commit 278086b

File tree

2 files changed

+48
-51
lines changed

2 files changed

+48
-51
lines changed

protosanitizer/protosanitizer.go

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -48,67 +48,68 @@ type stripSecrets struct {
4848
}
4949

5050
func (s *stripSecrets) String() string {
51-
// First convert to a generic representation. That's less efficient
52-
// than using reflect directly, but easier to work with.
53-
var parsed interface{}
54-
b, err := json.Marshal(s.msg)
55-
if err != nil {
56-
return fmt.Sprintf("<<json.Marshal %T: %s>>", s.msg, err)
57-
}
51+
stripped := s.msg
52+
53+
// also support scalar types like string, int, etc.
5854
msg, ok := s.msg.(proto.Message)
59-
if !ok {
60-
return string(b)
61-
}
62-
if err := json.Unmarshal(b, &parsed); err != nil {
63-
return fmt.Sprintf("<<json.Unmarshal %T: %s>>", s.msg, err)
55+
if ok {
56+
stripped = stripMessage(msg.ProtoReflect())
6457
}
6558

66-
// Now remove secrets from the generic representation of the message.
67-
s.strip(parsed, msg.ProtoReflect())
68-
69-
// Re-encoded the stripped representation and return that.
70-
b, err = json.Marshal(parsed)
59+
b, err := json.Marshal(stripped)
7160
if err != nil {
7261
return fmt.Sprintf("<<json.Marshal %T: %s>>", s.msg, err)
7362
}
7463
return string(b)
7564
}
7665

77-
func (s *stripSecrets) strip(parsed interface{}, msg protoreflect.Message) {
78-
// The corresponding map in the parsed JSON representation.
79-
parsedFields, ok := parsed.(map[string]interface{})
80-
if !ok {
81-
// Probably nil.
82-
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()
8374
}
75+
}
76+
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)
95+
}
96+
}
97+
98+
func stripMessage(msg protoreflect.Message) map[string]any {
99+
stripped := make(map[string]any)
84100

85101
// Walk through all fields and replace those with ***stripped*** that
86-
// are marked as secret. This relies on protobuf adding "json:" tags
87-
// on each field where the name matches the field name in the protobuf
88-
// spec (like volume_capabilities). The field.GetJsonName() method returns
89-
// a different name (volumeCapabilities) which we don't use.
102+
// are marked as secret.
90103
msg.Range(func(field protoreflect.FieldDescriptor, v protoreflect.Value) bool {
91104
name := field.TextName()
92105
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())
104-
}
105-
} else {
106-
// Single value.
107-
s.strip(entry, v.Message())
108-
}
106+
stripped[name] = "***stripped***"
107+
} else {
108+
stripped[name] = stripValue(field, v)
109109
}
110110
return true
111111
})
112+
return stripped
112113
}
113114

114115
// isCSI1Secret uses the csi.E_CsiSecret extension from CSI 1.0 to

protosanitizer/protosanitizer_test.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,11 @@ func TestStripSecrets(t *testing.T) {
142142
{true, "true"},
143143
{false, "false"},
144144
{&csi.CreateVolumeRequest{}, `{}`},
145-
{&testReq, `{"accessibility_requirements":{},"capacity_range":{"limit_bytes":1024,"required_bytes":1024},"name":"test-volume","parameters":{"param1":"param1","param2":"param2"},"secrets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4","mount_flags":["flag1","flag2","flag3"]}},"access_mode":{"mode":5}}],"volume_content_source":{"Type":null}}`},
146-
{createVolume, `{"accessibility_requirements":{"requisite":[{"segments":{"foo":"bar","x":"y"}},{"segments":{"a":"b"}}]},"capacity_range":{"required_bytes":1024},"name":"foo","secrets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}}}]}`},
145+
{&testReq, `{"accessibility_requirements":{},"capacity_range":{"limit_bytes":1024,"required_bytes":1024},"name":"test-volume","parameters":{"param1":"param1","param2":"param2"},"secrets":"***stripped***","volume_capabilities":[{"access_mode":{"mode":"MULTI_NODE_MULTI_WRITER"},"mount":{"fs_type":"ext4","mount_flags":["flag1","flag2","flag3"]}}],"volume_content_source":{}}`},
146+
{createVolume, `{"accessibility_requirements":{"requisite":[{"segments":{"foo":"bar","x":"y"}},{"segments":{"a":"b"}}]},"capacity_range":{"required_bytes":1024},"name":"foo","secrets":"***stripped***","volume_capabilities":[{"mount":{"fs_type":"ext4"}}]}`},
147147
{&csitest.CreateVolumeRequest{}, `{}`},
148148
{createVolumeFuture,
149-
// Secrets are *not* removed from all fields yet. This will have to be fixed one way or another
150-
// before the CSI spec can start using secrets there (currently it doesn't).
151-
// The test is still useful because it shows that also complicated fields get serialized.
152-
// `{"capacity_range":{"required_bytes":1024},"maybe_secret_map":{"1":{"AccessType":null,"array_secret":"***stripped***"},"2":{"AccessType":null,"array_secret":"***stripped***"}},"name":"foo","new_secret_int":"***stripped***","seecreets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}},"array_secret":"***stripped***"},{"AccessType":null,"array_secret":"***stripped***"}],"volume_content_source":{"Type":{"Volume":{"oneof_secret_field":"***stripped***","volume_id":"abc"}},"nested_secret_field":"***stripped***"}}`,
153-
`{"capacity_range":{"required_bytes":1024},"maybe_secret_map":{"1":{"AccessType":null,"array_secret":"aaa"},"2":{"AccessType":null,"array_secret":"bbb"}},"name":"foo","new_secret_int":"***stripped***","seecreets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}},"array_secret":"***stripped***"},{"AccessType":null,"array_secret":"***stripped***"}],"volume_content_source":{"Type":{"Volume":{"oneof_secret_field":"hello","volume_id":"abc"}},"nested_secret_field":"***stripped***"}}`,
149+
`{"capacity_range":{"required_bytes":1024},"maybe_secret_map":{"1":{"array_secret":"***stripped***"},"2":{"array_secret":"***stripped***"}},"name":"foo","new_secret_int":"***stripped***","seecreets":"***stripped***","volume_capabilities":[{"array_secret":"***stripped***","mount":{"fs_type":"ext4"}},{"array_secret":"***stripped***"}],"volume_content_source":{"nested_secret_field":"***stripped***","volume":{"oneof_secret_field":"***stripped***","volume_id":"abc"}}}`,
154150
},
155151
}
156152

@@ -161,7 +157,7 @@ func TestStripSecrets(t *testing.T) {
161157
if assert.NoError(t, err, "marshall future message") &&
162158
assert.NoError(t, proto.Unmarshal(data, unknownFields), "unmarshal with unknown fields") {
163159
cases = append(cases, testcase{unknownFields,
164-
`{"capacity_range":{"required_bytes":1024},"name":"foo","secrets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}}},{"AccessType":null}],"volume_content_source":{"Type":{"Volume":{"volume_id":"abc"}}}}`,
160+
`{"capacity_range":{"required_bytes":1024},"name":"foo","secrets":"***stripped***","volume_capabilities":[{"mount":{"fs_type":"ext4"}},{}],"volume_content_source":{"volume":{"volume_id":"abc"}}}`,
165161
})
166162
}
167163

0 commit comments

Comments
 (0)