-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Hidde Beydals <[email protected]>
- Loading branch information
Showing
16 changed files
with
2,613 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
Copyright 2023 The Flux 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 jsondiff | ||
|
||
import ( | ||
"github.com/wI2L/jsondiff" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
) | ||
|
||
// ChangeType is the type of change detected by the server-side apply diff | ||
// operation. | ||
type ChangeType string | ||
|
||
const ( | ||
// ChangeTypeCreate indicates that the resource does not exist | ||
// and needs to be created. | ||
ChangeTypeCreate ChangeType = "create" | ||
// ChangeTypeUpdate indicates that the resource exists and needs | ||
// to be updated. | ||
ChangeTypeUpdate ChangeType = "update" | ||
// ChangeTypeExclude indicates that the resource is excluded from | ||
// the diff. | ||
ChangeTypeExclude ChangeType = "exclude" | ||
// ChangeTypeNone indicates that the resource exists and is | ||
// identical to the dry-run object. | ||
ChangeTypeNone ChangeType = "none" | ||
) | ||
|
||
// Change is a change detected by the server-side apply diff operation. | ||
type Change struct { | ||
// Type of change detected. | ||
Type ChangeType | ||
|
||
// GroupVersionKind of the resource the Patch applies to. | ||
GroupVersionKind schema.GroupVersionKind | ||
|
||
// Namespace of the resource the Patch applies to. | ||
Namespace string | ||
|
||
// Name of the resource the Patch applies to. | ||
Name string | ||
|
||
// Patch with the changes detected for the resource. | ||
Patch jsondiff.Patch | ||
} | ||
|
||
// NewChangeForUnstructured creates a new Change for the given unstructured object. | ||
func NewChangeForUnstructured(obj *unstructured.Unstructured, t ChangeType, p jsondiff.Patch) *Change { | ||
return &Change{ | ||
Type: t, | ||
GroupVersionKind: obj.GetObjectKind().GroupVersionKind(), | ||
Namespace: obj.GetNamespace(), | ||
Name: obj.GetName(), | ||
Patch: p, | ||
} | ||
} | ||
|
||
// ChangeSet is a list of changes. | ||
type ChangeSet []*Change |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
Copyright 2023 The Flux 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 jsondiff | ||
|
||
import ( | ||
"github.com/wI2L/jsondiff" | ||
"strings" | ||
) | ||
|
||
const ( | ||
sensitiveMaskDefault = "***" | ||
sensitiveMaskBefore = "*** (before)" | ||
sensitiveMaskAfter = "*** (after)" | ||
) | ||
|
||
// MaskSecretPatchData masks the data and stringData fields of a Secret object | ||
// in the given JSON patch. It replaces the values with a default mask value if | ||
// the field is added or removed. Otherwise, it replaces the values with a | ||
// before/after mask value if the field is modified. | ||
func MaskSecretPatchData(patch jsondiff.Patch) jsondiff.Patch { | ||
for i := range patch { | ||
v := &patch[i] | ||
oldMaskValue, newMaskValue := sensitiveMaskDefault, sensitiveMaskDefault | ||
|
||
if v.OldValue != nil && v.Value != nil { | ||
oldMaskValue = sensitiveMaskBefore | ||
newMaskValue = sensitiveMaskAfter | ||
} | ||
|
||
switch { | ||
case v.Path == "/data" || v.Path == "/stringData": | ||
maskMap(v.OldValue, v.Value) | ||
case strings.HasPrefix(v.Path, "/data/") || strings.HasPrefix(v.Path, "/stringData/"): | ||
if v.OldValue != nil { | ||
v.OldValue = oldMaskValue | ||
} | ||
if v.Value != nil { | ||
v.Value = newMaskValue | ||
} | ||
} | ||
} | ||
return patch | ||
} | ||
|
||
// maskMap replaces the values with a default mask value if a field is added or | ||
// removed. Otherwise, it replaces the values with a before/after mask value if | ||
// the field is modified. | ||
func maskMap(from interface{}, to interface{}) { | ||
fromMap, fromIsMap := from.(map[string]interface{}) | ||
if !fromIsMap || fromMap == nil { | ||
fromMap = make(map[string]interface{}) | ||
} | ||
|
||
toMap, toIsMap := to.(map[string]interface{}) | ||
if !toIsMap || toMap == nil { | ||
toMap = make(map[string]interface{}) | ||
} | ||
|
||
for k := range fromMap { | ||
if _, ok := toMap[k]; ok { | ||
if fromMap[k] != toMap[k] { | ||
fromMap[k] = sensitiveMaskBefore | ||
toMap[k] = sensitiveMaskAfter | ||
continue | ||
} | ||
} | ||
fromMap[k] = sensitiveMaskDefault | ||
} | ||
for k := range toMap { | ||
if _, ok := fromMap[k]; !ok { | ||
toMap[k] = sensitiveMaskDefault | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
/* | ||
Copyright 2023 The Flux 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 jsondiff | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/wI2L/jsondiff" | ||
) | ||
|
||
func TestMaskSecretPatchData(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
patch jsondiff.Patch | ||
want jsondiff.Patch | ||
}{ | ||
{ | ||
name: "masks replace data values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/data/foo", OldValue: "bar", Value: "baz"}, | ||
{Type: jsondiff.OperationReplace, Path: "/data/bar", OldValue: "foo", Value: "baz"}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/data/foo", OldValue: sensitiveMaskBefore, Value: sensitiveMaskAfter}, | ||
{Type: jsondiff.OperationReplace, Path: "/data/bar", OldValue: sensitiveMaskBefore, Value: sensitiveMaskAfter}, | ||
}, | ||
}, | ||
{ | ||
name: "masks add data values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationAdd, Path: "/data/foo", Value: "baz"}, | ||
{Type: jsondiff.OperationAdd, Path: "/data/bar", Value: "baz"}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationAdd, Path: "/data/foo", Value: sensitiveMaskDefault}, | ||
{Type: jsondiff.OperationAdd, Path: "/data/bar", Value: sensitiveMaskDefault}, | ||
}, | ||
}, | ||
{ | ||
name: "masks remove data values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationRemove, Path: "/data/foo", OldValue: "bar"}, | ||
{Type: jsondiff.OperationRemove, Path: "/data/bar", OldValue: "foo"}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationRemove, Path: "/data/foo", OldValue: sensitiveMaskDefault}, | ||
{Type: jsondiff.OperationRemove, Path: "/data/bar", OldValue: sensitiveMaskDefault}, | ||
}, | ||
}, | ||
{ | ||
name: "masks rationalized replace data values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/data", OldValue: map[string]interface{}{ | ||
"foo": "bar", | ||
"bar": "foo", | ||
}, Value: map[string]interface{}{ | ||
"foo": "baz", | ||
"bar": "baz", | ||
}}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/data", OldValue: map[string]interface{}{ | ||
"foo": sensitiveMaskBefore, | ||
"bar": sensitiveMaskBefore, | ||
}, Value: map[string]interface{}{ | ||
"foo": sensitiveMaskAfter, | ||
"bar": sensitiveMaskAfter, | ||
}, | ||
}}, | ||
}, | ||
{ | ||
name: "masks rationalized add data values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationAdd, Path: "/data", Value: map[string]interface{}{ | ||
"foo": "baz", | ||
"bar": "baz", | ||
}}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationAdd, Path: "/data", Value: map[string]interface{}{ | ||
"foo": sensitiveMaskDefault, | ||
"bar": sensitiveMaskDefault, | ||
}}, | ||
}, | ||
}, | ||
{ | ||
name: "masks rationalized remove data values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationRemove, Path: "/data", OldValue: map[string]interface{}{ | ||
"foo": "bar", | ||
"bar": "foo", | ||
}}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationRemove, Path: "/data", OldValue: map[string]interface{}{ | ||
"foo": sensitiveMaskDefault, | ||
"bar": sensitiveMaskDefault, | ||
}}, | ||
}, | ||
}, | ||
{ | ||
name: "masks rationalized replace complex data values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/data", OldValue: map[string]interface{}{ | ||
// Changed key | ||
"foo": "bar", | ||
// Removed key | ||
"bar": "baz", | ||
}, Value: map[string]interface{}{ | ||
"foo": "baz", | ||
// Added key | ||
"baz": "bar", | ||
}}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/data", OldValue: map[string]interface{}{ | ||
"foo": sensitiveMaskBefore, | ||
"bar": sensitiveMaskDefault, | ||
}, Value: map[string]interface{}{ | ||
"foo": sensitiveMaskAfter, | ||
"baz": sensitiveMaskDefault, | ||
}}, | ||
}, | ||
}, | ||
{ | ||
name: "masks replace stringData values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/stringData/foo", OldValue: "bar", Value: "baz"}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationReplace, Path: "/stringData/foo", OldValue: sensitiveMaskBefore, Value: sensitiveMaskAfter}, | ||
}, | ||
}, | ||
{ | ||
name: "masks add stringData values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationAdd, Path: "/stringData/foo", Value: "baz"}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationAdd, Path: "/stringData/foo", Value: sensitiveMaskDefault}, | ||
}, | ||
}, | ||
{ | ||
name: "masks remove stringData values", | ||
patch: jsondiff.Patch{ | ||
{Type: jsondiff.OperationRemove, Path: "/stringData/foo", OldValue: "bar"}, | ||
}, | ||
want: jsondiff.Patch{ | ||
{Type: jsondiff.OperationRemove, Path: "/stringData/foo", OldValue: sensitiveMaskDefault}, | ||
}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := MaskSecretPatchData(tt.patch); !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("maskUnstructuredSecretData() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.