Skip to content

Commit 1628ab5

Browse files
authored
Merge pull request #1 from pohly/secrets-pr
never log secrets
2 parents b9fc19a + 2333c94 commit 1628ab5

File tree

549 files changed

+399355
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

549 files changed

+399355
-0
lines changed

Gopkg.lock

Lines changed: 154 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
2+
# for detailed Gopkg.toml documentation.
3+
4+
[prune]
5+
go-tests = true
6+
unused-packages = true

Makefile

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2018 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
.PHONY: all clean test
16+
17+
ifdef V
18+
TESTARGS = -v
19+
else
20+
TESTARGS =
21+
endif
22+
23+
all:
24+
go build `go list ./... | grep -v 'vendor'`
25+
26+
clean:
27+
true
28+
29+
test:
30+
go test `go list ./... | grep -v ^vendor` $(TESTARGS)
31+
go vet `go list ./... | grep -v ^vendor`
32+
diff="$$(gofmt -d $$(find . -name '*.go' | grep -v ^./vendor))" && \
33+
( [ -z "$$diff" ] || ( \
34+
echo "\nvvvvvv formatting errors, fix with patch -p1 vvvvvvvvvv\n$$diff\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"; \
35+
false ) )

protosanitizer/protosanitizer.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package protosanitizer supports logging of gRPC messages without
18+
// accidentally revealing sensitive fields.
19+
package protosanitizer
20+
21+
import (
22+
"encoding/json"
23+
"fmt"
24+
"reflect"
25+
"strings"
26+
27+
"github.com/container-storage-interface/spec/lib/go/csi"
28+
"github.com/golang/protobuf/descriptor"
29+
"github.com/golang/protobuf/proto"
30+
protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor"
31+
)
32+
33+
// StripSecrets returns a wrapper around the original CSI gRPC message
34+
// which has a Stringer implementation that serializes the message
35+
// as one-line JSON, but without including secret information.
36+
// Instead of the secret value(s), the string "***stripped***" is
37+
// included in the result.
38+
//
39+
// StripSecrets itself is fast and therefore it is cheap to pass the
40+
// result to logging functions which may or may not end up serializing
41+
// the parameter depending on the current log level.
42+
func StripSecrets(msg interface{}) fmt.Stringer {
43+
return &stripSecrets{msg}
44+
}
45+
46+
type stripSecrets struct {
47+
msg interface{}
48+
}
49+
50+
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+
}
58+
if err := json.Unmarshal(b, &parsed); err != nil {
59+
return fmt.Sprintf("<<json.Unmarshal %T: %s>>", s.msg, err)
60+
}
61+
62+
// Now remove secrets from the generic representation of the message.
63+
strip(parsed, s.msg)
64+
65+
// Re-encoded the stripped representation and return that.
66+
b, err = json.Marshal(parsed)
67+
if err != nil {
68+
return fmt.Sprintf("<<json.Marshal %T: %s>>", s.msg, err)
69+
}
70+
return string(b)
71+
}
72+
73+
func strip(parsed interface{}, msg interface{}) {
74+
protobufMsg, ok := msg.(descriptor.Message)
75+
if !ok {
76+
// Not a protobuf message, so we are done.
77+
return
78+
}
79+
80+
// The corresponding map in the parsed JSON representation.
81+
parsedFields, ok := parsed.(map[string]interface{})
82+
if !ok {
83+
// Probably nil.
84+
return
85+
}
86+
87+
// Walk through all fields and replace those with ***stripped*** that
88+
// are marked as secret. This relies on protobuf adding "json:" tags
89+
// on each field where the name matches the field name in the protobuf
90+
// spec (like volume_capabilities). The field.GetJsonName() method returns
91+
// a different name (volumeCapabilities) which we don't use.
92+
_, md := descriptor.ForMessage(protobufMsg)
93+
fields := md.GetField()
94+
if fields != nil {
95+
for _, field := range fields {
96+
ex, err := proto.GetExtension(field.Options, csi.E_CsiSecret)
97+
if err == nil && ex != nil && *ex.(*bool) {
98+
// Overwrite only if already set.
99+
if _, ok := parsedFields[field.GetName()]; ok {
100+
parsedFields[field.GetName()] = "***stripped***"
101+
}
102+
} else if field.GetType() == protobuf.FieldDescriptorProto_TYPE_MESSAGE {
103+
// When we get here,
104+
// the type name is something like ".csi.v1.CapacityRange" (leading dot!)
105+
// and looking up "csi.v1.CapacityRange"
106+
// returns the type of a pointer to a pointer
107+
// to CapacityRange. We need a pointer to such
108+
// a value for recursive stripping.
109+
typeName := field.GetTypeName()
110+
if strings.HasPrefix(typeName, ".") {
111+
typeName = typeName[1:]
112+
}
113+
t := proto.MessageType(typeName)
114+
if t == nil || t.Kind() != reflect.Ptr {
115+
// Shouldn't happen, but
116+
// better check anyway instead
117+
// of panicking.
118+
continue
119+
}
120+
v := reflect.New(t.Elem())
121+
122+
// Recursively strip the message(s) that
123+
// the field contains.
124+
i := v.Interface()
125+
entry := parsedFields[field.GetName()]
126+
if slice, ok := entry.([]interface{}); ok {
127+
// Array of values, like VolumeCapabilities in CreateVolumeRequest.
128+
for _, entry := range slice {
129+
strip(entry, i)
130+
}
131+
} else {
132+
// Single value.
133+
strip(entry, i)
134+
}
135+
}
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)