Skip to content

Commit

Permalink
Redact entity configuration metadata (#4862)
Browse files Browse the repository at this point in the history
Heavy merge guessing by CK.

Signed-off-by: James Phillips <jamesdphillips@gmail.com>
(cherry picked from commit 7a13587)
  • Loading branch information
jamesdphillips authored and c-kruse committed Oct 5, 2022
1 parent b3807f3 commit 41f3e41
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 14 deletions.
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
}
19 changes: 17 additions & 2 deletions backend/api/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,13 @@ func (g *GenericClient) getResource(ctx context.Context, name string, value core
if err != nil {
return err
}
return wrapper.UnwrapInto(value)
if err := wrapper.UnwrapInto(value); err != nil {
return err
}
if redacter, ok := value.(corev3.Redacter); ok {
value = redacter.ProduceRedacted()
}
return err
}

// Get gets a resource, if authorized
Expand Down Expand Up @@ -184,7 +190,16 @@ func (g *GenericClient) list(ctx context.Context, resources interface{}, pred *s
if err != nil {
return err
}
return list.UnwrapInto(resources)

if err := list.UnwrapInto(resources); err != nil {
return err
}
if redacters, ok := resources.([]corev3.Redacter); ok {
for i, redacter := range redacters {
redacters[i] = redacter.ProduceRedacted()
}
}
return nil
}

// List lists all resources within a namespace, according to a selection
Expand Down
Loading

0 comments on commit 41f3e41

Please sign in to comment.