Merge pull request #96097 from rf232/datapol

Introduce a simple datapolicy library
This commit is contained in:
Kubernetes Prow Robot 2020-11-05 10:53:20 -08:00 committed by GitHub
commit 569abbeb4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 385 additions and 0 deletions

View File

@ -34,6 +34,7 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/component-base/logs/datapol:all-srcs",
"//staging/src/k8s.io/component-base/logs/json:all-srcs",
"//staging/src/k8s.io/component-base/logs/logreduction:all-srcs",
],

View File

@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"datapol.go",
"externaltypes.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/component-base/logs/datapol",
importpath = "k8s.io/component-base/logs/datapol",
visibility = ["//visibility:public"],
deps = ["//vendor/k8s.io/klog/v2:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = [
"datapol_test.go",
"externaltypes_test.go",
],
embed = [":go_default_library"],
deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,99 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package datapol contains functions to determine if objects contain sensitive
// data to e.g. make decisions on whether to log them or not.
package datapol
import (
"reflect"
"strings"
"k8s.io/klog/v2"
)
// Verify returns a list of the datatypes contained in the argument that can be
// considered sensitive w.r.t. to logging
func Verify(value interface{}) []string {
defer func() {
if r := recover(); r != nil {
//TODO maybe export a metric
klog.Warningf("Error while inspecting arguments for sensitive data: %v", r)
}
}()
t := reflect.ValueOf(value)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return datatypes(t)
}
func datatypes(v reflect.Value) []string {
if types := byType(v.Type()); len(types) > 0 {
// Slices, and maps can be nil or empty, only the nil case is zero
switch v.Kind() {
case reflect.Slice, reflect.Map:
if !v.IsZero() && v.Len() > 0 {
return types
}
default:
if !v.IsZero() {
return types
}
}
}
switch v.Kind() {
case reflect.Interface:
return datatypes(v.Elem())
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
if types := datatypes(v.Index(i)); len(types) > 0 {
return types
}
}
case reflect.Map:
mapIter := v.MapRange()
for mapIter.Next() {
k := mapIter.Key()
v := mapIter.Value()
if types := datatypes(k); len(types) > 0 {
return types
}
if types := datatypes(v); len(types) > 0 {
return types
}
}
case reflect.Struct:
t := v.Type()
numField := t.NumField()
for i := 0; i < numField; i++ {
f := t.Field(i)
if f.Type.Kind() == reflect.Ptr {
continue
}
if reason, ok := f.Tag.Lookup("datapolicy"); ok {
if !v.Field(i).IsZero() {
return strings.Split(reason, ",")
}
}
if types := datatypes(v.Field(i)); len(types) > 0 {
return types
}
}
}
return nil
}

View File

@ -0,0 +1,152 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package datapol
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
const (
marker = "hunter2"
)
type withDatapolTag struct {
Key string `json:"key" datapolicy:"password"`
}
type withExternalType struct {
Header http.Header `json:"header"`
}
type noDatapol struct {
Key string `json:"key"`
}
type datapolInMember struct {
secrets withDatapolTag
}
type datapolInSlice struct {
secrets []withDatapolTag
}
type datapolInMap struct {
secrets map[string]withDatapolTag
}
type datapolBehindPointer struct {
secrets *withDatapolTag
}
func TestValidate(t *testing.T) {
testcases := []struct {
name string
value interface{}
expect []string
badFilter bool
}{{
name: "Empty password",
value: withDatapolTag{},
expect: []string{},
}, {
name: "Non-empty password",
value: withDatapolTag{
Key: marker,
},
expect: []string{"password"},
}, {
name: "empty external type",
value: withExternalType{Header: http.Header{}},
expect: []string{},
}, {
name: "external type",
value: withExternalType{Header: http.Header{
"Authorization": []string{"Bearer hunter2"},
}},
expect: []string{"password", "token"},
}, {
name: "no datapol tag",
value: noDatapol{Key: marker},
expect: []string{},
badFilter: true,
}, {
name: "nested",
value: datapolInMember{
secrets: withDatapolTag{
Key: marker,
},
},
expect: []string{"password"},
}, {
name: "nested in pointer",
value: datapolBehindPointer{
secrets: &withDatapolTag{Key: marker},
},
expect: []string{},
}, {
name: "nested in slice",
value: datapolInSlice{
secrets: []withDatapolTag{{Key: marker}},
},
expect: []string{"password"},
}, {
name: "nested in map",
value: datapolInMap{
secrets: map[string]withDatapolTag{
"key": {Key: marker},
},
},
expect: []string{"password"},
}, {
name: "nested in map but empty",
value: datapolInMap{
secrets: map[string]withDatapolTag{
"key": {},
},
},
expect: []string{},
}, {
name: "struct in interface",
value: struct{ v interface{} }{v: withDatapolTag{
Key: marker,
}},
expect: []string{"password"},
}, {
name: "structptr in interface",
value: struct{ v interface{} }{v: &withDatapolTag{
Key: marker,
}},
expect: []string{},
}}
for _, tc := range testcases {
res := Verify(tc.value)
if !assert.ElementsMatch(t, tc.expect, res) {
t.Errorf("Wrong set of tags for %q. expect %v, got %v", tc.name, tc.expect, res)
}
if !tc.badFilter {
formatted := fmt.Sprintf("%v", tc.value)
if strings.Contains(formatted, marker) != (len(tc.expect) > 0) {
t.Errorf("Filter decision doesn't match formatted value for %q: tags: %v, format: %s", tc.name, tc.expect, formatted)
}
}
}
}

View File

@ -0,0 +1,49 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package datapol
import (
"fmt"
"reflect"
)
const (
httpHeader = "net/http.Header"
httpCookie = "net/http.Cookie"
x509Certificate = "crypto/x509.Certificate"
)
// GlobalDatapolicyMapping returns the list of sensitive datatypes are embedded
// in types not native to Kubernetes.
func GlobalDatapolicyMapping(v interface{}) []string {
return byType(reflect.TypeOf(v))
}
func byType(t reflect.Type) []string {
// Use string representation of the type to prevent taking a depency on the actual type.
switch fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) {
case httpHeader:
return []string{"password", "token"}
case httpCookie:
return []string{"token"}
case x509Certificate:
return []string{"security-key"}
default:
return nil
}
}

View File

@ -0,0 +1,47 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package datapol
import (
"crypto/x509"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTypes(t *testing.T) {
testcases := []struct {
value interface{}
expect []string
}{{
value: http.Header{},
expect: []string{"password", "token"},
}, {
value: http.Cookie{},
expect: []string{"token"},
}, {
value: x509.Certificate{},
expect: []string{"security-key"},
}}
for _, tc := range testcases {
types := GlobalDatapolicyMapping(tc.value)
if !assert.ElementsMatch(t, tc.expect, types) {
t.Errorf("Wrong set of datatypes detected for %T, want: %v, got %v", tc.value, tc.expect, types)
}
}
}