mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +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",
|
name = "all-srcs",
|
||||||
srcs = [
|
srcs = [
|
||||||
":package-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/json:all-srcs",
|
||||||
"//staging/src/k8s.io/component-base/logs/logreduction: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