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

Redact entity configuration metadata #4862

Merged
merged 8 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions api/core/v3/entity_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

corev2 "github.com/sensu/sensu-go/api/core/v2"
stringutil "github.com/sensu/sensu-go/api/core/v3/internal/strings"
)

var entityConfigRBACName = (&corev2.Entity{}).RBACName()
Expand All @@ -31,3 +32,35 @@ func MergeMapWithPrefix(a map[string]string, b map[string]string, prefix string)
a[prefix+k] = v
}
}

func redactMap(m map[string]string, redact []string) map[string]string {
if len(redact) == 0 {
redact = corev2.DefaultRedactFields
}
result := make(map[string]string, len(m))
for k, v := range m {
if stringutil.FoundInArray(k, redact) {
result[k] = corev2.Redacted
} else {
result[k] = v
}
}
return result
}

// ProduceRedacted redacts the entity according to the entity's Redact fields.
// A redacted copy is returned. The copy contains pointers to the original's
// memory, with different Labels and Annotations.
func (e *EntityConfig) ProduceRedacted() Resource {
if e == nil {
return nil
}
if e.Metadata == nil || (e.Metadata.Labels == nil && e.Metadata.Annotations == nil) {
return e
}
copy := &EntityConfig{}
*copy = *e
copy.Metadata.Annotations = redactMap(e.Metadata.Annotations, e.Redact)
copy.Metadata.Labels = redactMap(e.Metadata.Labels, e.Redact)
return copy
}
85 changes: 80 additions & 5 deletions api/core/v3/entity_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"reflect"
"testing"

v2 "github.com/sensu/sensu-go/api/core/v2"
corev2 "github.com/sensu/sensu-go/api/core/v2"
)

func TestEntityConfigFields(t *testing.T) {
Expand All @@ -22,26 +22,26 @@ func TestEntityConfigFields(t *testing.T) {
},
{
name: "exposes deregister",
args: &EntityConfig{Metadata: &v2.ObjectMeta{}, Deregister: true},
args: &EntityConfig{Metadata: &corev2.ObjectMeta{}, Deregister: true},
wantKey: "entity_config.deregister",
want: "true",
},
{
name: "exposes class",
args: &EntityConfig{Metadata: &v2.ObjectMeta{}, EntityClass: "agent"},
args: &EntityConfig{Metadata: &corev2.ObjectMeta{}, EntityClass: "agent"},
wantKey: "entity_config.entity_class",
want: "agent",
},
{
name: "exposes subscriptions",
args: &EntityConfig{Metadata: &v2.ObjectMeta{}, Subscriptions: []string{"www", "unix"}},
args: &EntityConfig{Metadata: &corev2.ObjectMeta{}, Subscriptions: []string{"www", "unix"}},
wantKey: "entity_config.subscriptions",
want: "www,unix",
},
{
name: "exposes labels",
args: &EntityConfig{
Metadata: &v2.ObjectMeta{
Metadata: &corev2.ObjectMeta{
Labels: map[string]string{"region": "philadelphia"},
},
},
Expand All @@ -58,3 +58,78 @@ func TestEntityConfigFields(t *testing.T) {
})
}
}

func TestEntityConfig_ProduceRedacted(t *testing.T) {
tests := []struct {
name string
in *EntityConfig
want *EntityConfig
}{
{
name: "nil metadata",
in: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Metadata = nil
return cfg
}(),
want: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Metadata = nil
return cfg
}(),
},
{
name: "nothing to redact",
in: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Metadata.Labels["my_field"] = "test123"
return cfg
}(),
want: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Metadata.Labels["my_field"] = "test123"
return cfg
}(),
},
{
name: "redact default fields",
in: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Metadata.Labels["my_field"] = "test123"
cfg.Metadata.Labels["password"] = "test123"
return cfg
}(),
want: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Metadata.Labels["my_field"] = "test123"
cfg.Metadata.Labels["password"] = corev2.Redacted
return cfg
}(),
},
{
name: "redact custom fields",
in: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Redact = []string{"my_field"}
cfg.Metadata.Labels["my_field"] = "test123"
cfg.Metadata.Labels["password"] = "test123"
return cfg
}(),
want: func() *EntityConfig {
cfg := FixtureEntityConfig("test")
cfg.Redact = []string{"my_field"}
cfg.Metadata.Labels["my_field"] = corev2.Redacted
cfg.Metadata.Labels["password"] = "test123"
return cfg
}(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := tt.in
if got := e.ProduceRedacted(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("EntityConfig.ProduceRedacted() = %v, want %v", got, tt.want)
}
})
}
}
60 changes: 60 additions & 0 deletions api/core/v3/internal/strings/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package strings

import (
"strings"
"unicode"
)

const (
lowerAlphaStart = 97
lowerAlphaStop = 122
)

func isAlpha(r rune) bool {
return r >= lowerAlphaStart && r <= lowerAlphaStop
}

func alphaNumeric(s string) bool {
for _, r := range s {
if !(unicode.IsDigit(r) || isAlpha(r)) {
return false
}
}
return true
}

func normalize(s string) string {
if alphaNumeric(s) {
return s
}
lowered := strings.ToLower(s)
if alphaNumeric(lowered) {
return lowered
}
trimmed := make([]rune, 0, len(lowered))
for _, r := range lowered {
if isAlpha(r) {
trimmed = append(trimmed, r)
}
}
return string(trimmed)
}

// FoundInArray searches array for item without distinguishing between uppercase
// and lowercase and non-alphanumeric characters. Returns true if item is a
// value of array
func FoundInArray(item string, array []string) bool {
if item == "" || len(array) == 0 {
return false
}

item = normalize(item)

for i := range array {
if normalize(array[i]) == item {
return true
}
}

return false
}
34 changes: 34 additions & 0 deletions api/core/v3/internal/strings/strings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package strings

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestFoundInArray(t *testing.T) {
var array []string

found := FoundInArray("Foo", []string{})
assert.False(t, found)

array = []string{"foo", "bar"}
found = FoundInArray("Foo", array)
assert.True(t, found)

array = []string{"foo", "bar"}
found = FoundInArray("FooBar", array)
assert.False(t, found)

array = []string{"foo", "bar"}
found = FoundInArray("Foo ", array)
assert.True(t, found)

array = []string{"foo_bar"}
found = FoundInArray("Foo_Bar", array)
assert.True(t, found)

array = []string{"foobar"}
found = FoundInArray("Foo_Qux", array)
assert.False(t, found)
}
7 changes: 7 additions & 0 deletions api/core/v3/redacter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package v3

// Redacter can return a redacted copy of the resource
type Redacter interface {
// ProduceRedacted returns a redacted copy of the resource
ProduceRedacted() Resource
}
14 changes: 12 additions & 2 deletions backend/api/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,13 @@ func (g *GenericClient) getResource(ctx context.Context, name string, value core
if err != nil {
return err
}
return wrapper.UnwrapInto(value.Resource)
if err := wrapper.UnwrapInto(value.Resource); err != nil {
return err
}
if redacter, ok := value.Resource.(corev3.Redacter); ok {
value.Resource = redacter.ProduceRedacted()
}
return err
}
return g.Store.GetResource(ctx, name, value)
}
Expand Down Expand Up @@ -209,7 +215,11 @@ func (g *GenericClient) list(ctx context.Context, resources interface{}, pred *s
}
v2values := make([]corev2.Resource, len(values))
for i := range values {
v2values[i] = corev3.V3ToV2Resource(values[i])
resource := values[i]
if redacter, ok := resource.(corev3.Redacter); ok {
resource = redacter.ProduceRedacted()
}
v2values[i] = corev3.V3ToV2Resource(resource)
}
*resourceList = v2values
return nil
Expand Down
Loading