Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

never log secrets #171

Closed
wants to merge 1 commit into from
Closed

Conversation

pohly
Copy link
Contributor

@pohly pohly commented Nov 21, 2018

When running at glog level >= 5, external-provisioner logged the full
CreateVolumeRequest, including the secrets. Secrets should never be
logged at any level to avoid accidentally exposing them.

We need to filter out the secrets. With older CSI versions, that could
have been done based on the field name, which is still an option
should this get backported. With CSI 1.0, a custom field option marks
fields as secret. Using that option has the advantage that the code
will continue to work also when new secret fields get added in the
future.

For the sake of simplicity, JSON is now used as representation of the
string instead of the former compact text format from gRPC. That makes
it possible to strip values from a map with generic types, instead of
having to copy and manipulate the real generated structures.

Another option would have been to copy
https://github.com/golang/protobuf/blob/master/proto/text.go and
modify it so that it skips secret fields, but that's over 800 lines of
code.

Ultimately this new package should live in a "csi-common" repo and
also include other utility code, like logGRPC itself.

Fixes: #82, #167

When running at glog level >= 5, external-provisioner logged the full
CreateVolumeRequest, including the secrets. Secrets should never be
logged at any level to avoid accidentally exposing them.

We need to filter out the secrets. With older CSI versions, that could
have been done based on the field name, which is still an option
should this get backported. With CSI 1.0, a custom field option marks
fields as secret. Using that option has the advantage that the code
will continue to work also when new secret fields get added in the
future.

For the sake of simplicity, JSON is now used as representation of the
string instead of the former compact text format from gRPC. That makes
it possible to strip values from a map with generic types, instead of
having to copy and manipulate the real generated structures.

Another option would have been to copy
https://github.com/golang/protobuf/blob/master/proto/text.go and
modify it so that it skips secret fields, but that's over 800 lines of
code.

Ultimately this new package should live in a "csi-common" repo and
also include other utility code, like logGRPC itself.

Fixes: kubernetes-csi#82, kubernetes-csi#167
@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Nov 21, 2018
@pohly
Copy link
Contributor Author

pohly commented Nov 21, 2018

/assign @saad-ali

@sbezverk
Copy link
Contributor

@pohly is it really required to go through marshal/unmarshal? Would it not be sufficient just to strip secret and convert it into a string for printing it in the log?
Also giving package csigrpc name imho is a bit misleading, I think this function belongs more to utils/tools package.

@pohly
Copy link
Contributor Author

pohly commented Nov 21, 2018 via email

@@ -102,6 +102,7 @@
digest = "1:3dd078fda7500c341bc26cfbc6c6a34614f295a2457149fc1045cab767cbcf18"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did Gopkg.toml also need to be modified?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It gets updated by "dep" when the set of input files changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we now have a conflict because of that. I can rebase if this approach is how we want to continue.

pkg/csigrpc/csigrpc.go Show resolved Hide resolved
pkg/csigrpc/csigrpc.go Show resolved Hide resolved
pkg/csigrpc/csigrpc.go Show resolved Hide resolved
ex, err := proto.GetExtension(field.Options, csi.E_CsiSecret)
if err == nil && ex != nil && *ex.(*bool) {
parsedFields[field.GetName()] = "***stripped***"
} else if field.GetType() == protobuf.FieldDescriptorProto_TYPE_MESSAGE {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if err != nil do we still want to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked in detail which errors might be returned by proto.GetExtension. My assumption here is that we won't get an error for a field that has the csi_secret option set and that therefore getting an error indicates that it isn't a secret field. Under that assumption it is okay and actually desirable to continue filtering that field recursively despite an error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, sounds good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed normal to get errors. For the "name" field:

(dlv) p err
error(*errors.errorString) *{
	s: "proto: nil *descriptor.FieldOptions is not extendable",}
(dlv) p field
*github.com/kubernetes-csi/external-provisioner/vendor/github.com/golang/protobuf/protoc-gen-go/descriptor.FieldDescriptorProto {
	Name: *"name",
	Number: *1,
...

For "name" we don't need to recurse, but the same error also occurs for "capacity_range", and in that case we must ignore the error and filter CapacityRange.

@sbezverk
Copy link
Contributor

@pohly I would like to propose an alternative solution which I think is less complex and should be more performant. I hope you do not mind.

@pohly
Copy link
Contributor Author

pohly commented Nov 22, 2018 via email

@pohly
Copy link
Contributor Author

pohly commented Nov 26, 2018 via email

@sbezverk
Copy link
Contributor

@pohly it is ready for review, it is just a matter of creating repo csi-utils as per @saad-ali suggestion.

@pohly
Copy link
Contributor Author

pohly commented Nov 26, 2018 via email

@sbezverk
Copy link
Contributor

@pohly here you go. kubernetes/kubernetes#71336

@pohly
Copy link
Contributor Author

pohly commented Nov 26, 2018 via email

@pohly
Copy link
Contributor Author

pohly commented Nov 26, 2018 via email

@@ -119,9 +120,9 @@ var (
//TODO consolidate ane librarize
func logGRPC(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
glog.V(5).Infof("GRPC call: %s", method)
glog.V(5).Infof("GRPC request: %+v", req)
glog.V(5).Infof("GRPC request: %s", csigrpc.StripSecrets(req))
Copy link
Contributor

@jsafrane jsafrane Nov 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for performance:

if glog.V(5) {
    glog.Infof("GRPC request: %s", csigrpc.StripSecrets(req))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessary, csigrpc.StripSecrets was explicitly designed to not run the expensive string conversion at the time when Infof gets called.

"testing"

"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/stretchr/testify/assert"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's worth adding new dependency for few asserts below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, for the external-attacher repo a simpler test would be better. But the code was meant to land in its own repo and now it looks like we'll do that right away (see kubernetes/org#268) and there I think it makes sense to use good testing infrastructure. The output of assert.Equal is much more readable than just printing the strings in manually-crafted failure messages.

// The secret is hidden because StripSecrets is a struct referencing it.
dump := fmt.Sprintf("%#v", StripSecrets(createVolume))
assert.NotContains(t, secretName, dump)
assert.NotContains(t, secretValue, dump)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these assertions are backwards... should be assert.NotContains(t, dump, secretName)

}

// Walk through all fields and replace those with ***stripped*** that
// are marked as secret. This relies on protobuf adding "json:" tags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relying on the json/proto correlation seems particularly fragile... is there a reason not to copy the proto message and walk it instead?

@pohly
Copy link
Contributor Author

pohly commented Nov 28, 2018 via email

@pohly
Copy link
Contributor Author

pohly commented Nov 28, 2018 via email

secretValue := "123"
createVolume := &csi.CreateVolumeRequest{
Name: "foo",
VolumeCapabilities: []*csi.VolumeCapability{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add topology into your unit test? It's one of the more complicated structures in the csi proto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pohly added a commit to pohly/csi-lib-utils that referenced this pull request Nov 29, 2018
When running at glog level >= 5, CSI sidecar apps log the full
CreateVolumeRequest, including the secrets. Secrets should never be
logged at any level to avoid accidentally exposing them.

We need to filter out the secrets. With older CSI versions, that could
have been done based on the field name, which is still an option
should this get backported. With CSI 1.0, a custom field option marks
fields as secret. Using that option has the advantage that the code
will continue to work also when new secret fields get added in the
future.

For the sake of simplicity, JSON is now used as representation of the
string instead of the former compact text format from gRPC. That makes
it possible to strip values from a map with generic types, instead of
having to copy and manipulate the real generated structures.

Another option would have been to copy
https://github.com/golang/protobuf/blob/master/proto/text.go and
modify it so that it skips secret fields, but that's over 800 lines of
code.

This version of the code is identical to the one reviewed in
kubernetes-csi/external-provisioner#171 with
the backwards parameters in
assert.NotContains (kubernetes-csi/external-provisioner#171 (review))
fixed.
pohly added a commit to pohly/csi-lib-utils that referenced this pull request Nov 29, 2018
When running at glog level >= 5, CSI sidecar apps log the full
CreateVolumeRequest, including the secrets. Secrets should never be
logged at any level to avoid accidentally exposing them.

We need to filter out the secrets. With older CSI versions, that could
have been done based on the field name, which is still an option
should this get backported. With CSI 1.0, a custom field option marks
fields as secret. Using that option has the advantage that the code
will continue to work also when new secret fields get added in the
future.

For the sake of simplicity, JSON is now used as representation of the
string instead of the former compact text format from gRPC. That makes
it possible to strip values from a map with generic types, instead of
having to copy and manipulate the real generated structures.

Another option would have been to copy
https://github.com/golang/protobuf/blob/master/proto/text.go and
modify it so that it skips secret fields, but that's over 800 lines of
code.

This version of the code is identical to the one reviewed in
kubernetes-csi/external-provisioner#171 with
the backwards parameters in
assert.NotContains (kubernetes-csi/external-provisioner#171 (review))
fixed.
pohly added a commit to pohly/csi-lib-utils that referenced this pull request Nov 29, 2018
When running at glog level >= 5, CSI sidecar apps log the full
CreateVolumeRequest, including the secrets. Secrets should never be
logged at any level to avoid accidentally exposing them.

We need to filter out the secrets. With older CSI versions, that could
have been done based on the field name, which is still an option
should this get backported. With CSI 1.0, a custom field option marks
fields as secret. Using that option has the advantage that the code
will continue to work also when new secret fields get added in the
future.

For the sake of simplicity, JSON is now used as representation of the
string instead of the former compact text format from gRPC. That makes
it possible to strip values from a map with generic types, instead of
having to copy and manipulate the real generated structures.

Another option would have been to copy
https://github.com/golang/protobuf/blob/master/proto/text.go and
modify it so that it skips secret fields, but that's over 800 lines of
code.

This version of the code is identical to the one reviewed in
kubernetes-csi/external-provisioner#171 with
the backwards parameters in
assert.NotContains (kubernetes-csi/external-provisioner#171 (review))
fixed.
@pohly
Copy link
Contributor Author

pohly commented Nov 29, 2018

/hold

kubernetes-csi/csi-lib-utils#1 needs to be merged first, then we can vendor that code into this PR.

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Nov 29, 2018
pohly added a commit to pohly/csi-lib-utils that referenced this pull request Nov 29, 2018
When running at glog level >= 5, CSI sidecar apps log the full
CreateVolumeRequest, including the secrets. Secrets should never be
logged at any level to avoid accidentally exposing them.

We need to filter out the secrets. With older CSI versions, that could
have been done based on the field name, which is still an option
should this get backported. With CSI 1.0, a custom field option marks
fields as secret. Using that option has the advantage that the code
will continue to work also when new secret fields get added in the
future.

For the sake of simplicity, JSON is now used as representation of the
string instead of the former compact text format from gRPC. That makes
it possible to strip values from a map with generic types, instead of
having to copy and manipulate the real generated structures.

Another option would have been to copy
https://github.com/golang/protobuf/blob/master/proto/text.go and
modify it so that it skips secret fields, but that's over 800 lines of
code.

This version of the code is identical to the one reviewed in
kubernetes-csi/external-provisioner#171 with
the backwards parameters in
assert.NotContains (kubernetes-csi/external-provisioner#171 (review))
fixed.
pohly added a commit to pohly/csi-lib-utils that referenced this pull request Nov 30, 2018
When running at glog level >= 5, CSI sidecar apps log the full
CreateVolumeRequest, including the secrets. Secrets should never be
logged at any level to avoid accidentally exposing them.

We need to filter out the secrets. With older CSI versions, that could
have been done based on the field name, which is still an option
should this get backported. With CSI 1.0, a custom field option marks
fields as secret. Using that option has the advantage that the code
will continue to work also when new secret fields get added in the
future.

For the sake of simplicity, JSON is now used as representation of the
string instead of the former compact text format from gRPC. That makes
it possible to strip values from a map with generic types, instead of
having to copy and manipulate the real generated structures.

Another option would have been to copy
https://github.com/golang/protobuf/blob/master/proto/text.go and
modify it so that it skips secret fields, but that's over 800 lines of
code.

This version of the code is identical to the one reviewed in
kubernetes-csi/external-provisioner#171 with
the backwards parameters in
assert.NotContains (kubernetes-csi/external-provisioner#171 (review))
fixed.
@pohly
Copy link
Contributor Author

pohly commented Dec 3, 2018

The code was moved to kubernetes-csi/csi-lib-utils#1 and later merged into external-provisioner in #179.

@pohly pohly closed this Dec 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants