mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #96097 from rf232/datapol
Introduce a simple datapolicy library
This commit is contained in:
commit
569abbeb4e
@ -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",
|
||||
],
|
||||
|
37
staging/src/k8s.io/component-base/logs/datapol/BUILD
Normal file
37
staging/src/k8s.io/component-base/logs/datapol/BUILD
Normal 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"],
|
||||
)
|
99
staging/src/k8s.io/component-base/logs/datapol/datapol.go
Normal file
99
staging/src/k8s.io/component-base/logs/datapol/datapol.go
Normal 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
|
||||
}
|
152
staging/src/k8s.io/component-base/logs/datapol/datapol_test.go
Normal file
152
staging/src/k8s.io/component-base/logs/datapol/datapol_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user